Skip to content

feat(usage): add feature attribution to microdollar usage#307

Merged
pedroheyerdahl merged 8 commits intomainfrom
feat/usage-feature-attribution
Feb 18, 2026
Merged

feat(usage): add feature attribution to microdollar usage#307
pedroheyerdahl merged 8 commits intomainfrom
feat/usage-feature-attribution

Conversation

@pedroheyerdahl
Copy link
Contributor

@pedroheyerdahl pedroheyerdahl commented Feb 17, 2026

Adds a nullable feature column to microdollar_usage so we can track which product feature generated each token usage record. This is the backend portion of a cross-repo change.

What this does:

  • Adds feature column to microdollar_usage table and microdollar_usage_view
  • Creates src/lib/feature-detection.ts with an allow-list of 14 feature values and a validateFeatureHeader() function
  • Both gateway endpoints (/api/openrouter and /api/fim/completions) read the new X-KILOCODE-FEATURE header and store the validated value
  • sendProxiedChatCompletion accepts an optional feature field and forwards it as a header
  • Security agent and slack bot callers send their respective feature values
  • cloud-agent-next server manager accepts an optional feature parameter to set KILOCODE_FEATURE env var when launching kilo serve

How it works:
Callers send X-KILOCODE-FEATURE: <value>. The gateway validates against the allow-list and stores it. No header = NULL. No fallback heuristics in the write path.

Companion changes needed (separate PRs):

  • Kilo-Org/kilo: Add HEADER_FEATURE constant to kilo-gateway, read KILOCODE_FEATURE env var
  • Kilo-Org/kilocode: Add X-KiloCode-Feature header to customRequestOptions() and streamFim()

See plans/microdollar-feature-tracking.md for the full implementation plan.


@pedroheyerdahl pedroheyerdahl force-pushed the feat/usage-feature-attribution branch from 51ebabc to 2fc5096 Compare February 17, 2026 23:57
@pedroheyerdahl pedroheyerdahl force-pushed the feat/usage-feature-attribution branch 3 times, most recently from ea84e39 to 057da53 Compare February 18, 2026 00:59
@pedroheyerdahl pedroheyerdahl force-pushed the feat/usage-feature-attribution branch from 057da53 to c180567 Compare February 18, 2026 01:32
@pedroheyerdahl pedroheyerdahl force-pushed the feat/usage-feature-attribution branch from 1caac0a to 3459b8e Compare February 18, 2026 01:44
@Kilo-Org Kilo-Org deleted a comment from kiloconnect bot Feb 18, 2026
@kiloconnect
Copy link
Contributor

kiloconnect bot commented Feb 18, 2026

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Clean implementation of feature attribution for microdollar usage tracking. The PR adds a feature lookup table with FK to microdollar_usage_metadata, validates the X-KILOCODE-FEATURE header via zod at both gateway endpoints, threads the value through the full processUsage pipeline using the existing createUpsertCTE pattern, and sets KILOCODE_FEATURE env vars for cloud-agent sessions. All feature string literals match the FEATURE_VALUES allow-list and are type-checked via FeatureValue.

Files Reviewed (18 files)
  • src/lib/feature-detection.ts - New file: feature allow-list, zod validation, header constant
  • src/lib/processUsage.ts - Added feature to usage context, metadata type, and SQL insert
  • src/lib/processUsage.test.ts - Test context updated with feature field
  • src/lib/llm-proxy-helpers.ts - Added feature to ProxiedChatCompletionRequest, forwarded as header
  • src/app/api/openrouter/[...path]/route.ts - Extracts and validates feature header
  • src/app/api/gateway/[...path]/route.ts - Unchanged (re-export; direct-gateway injection deferred per discussion)
  • src/db/schema.ts - Added feature table, feature_id FK, updated view
  • src/db/migrations/0018_colossal_professor_monster.sql - Migration: create table, add column, recreate view
  • src/db/migrations/meta/_journal.json - Journal entry for migration 0018
  • cloud-agent-next/src/session-service.ts - Sets KILOCODE_FEATURE env var from createdOnPlatform
  • cloud-agent-next/src/session-service.test.ts - Test expectations updated
  • cloud-agent/src/session-service.ts - Sets KILOCODE_FEATURE env var from createdOnPlatform
  • cloud-agent/src/session-service.test.ts - Test expectations updated
  • cloudflare-auto-fix-infra/src/fix-orchestrator.ts - Passes createdOnPlatform: 'autofix'
  • cloudflare-auto-triage-infra/src/triage-orchestrator.ts - Passes createdOnPlatform: 'auto-triage'
  • cloudflare-code-review-infra/src/code-review-orchestrator.ts - Passes createdOnPlatform: 'code-review'
  • kiloclaw/src/gateway/env.ts - Sets KILOCODE_FEATURE=kilo-claw
  • src/lib/security-agent/services/extraction-service.ts - Passes feature: 'security-agent'
  • src/lib/security-agent/services/triage-service.ts - Passes feature: 'security-agent'
  • src/lib/slack-bot.ts - Passes feature: 'slack'
  • src/scripts/usage/benchmark-insert-usage.ts - Benchmark updated with feature field
  • src/tests/helpers/microdollar-usage.helper.ts - Test helpers updated
  • plans/microdollar-feature-tracking.md - Comprehensive plan documentation
  • src/db/migrations/meta/0018_snapshot.json - Migration snapshot (generated)

@@ -1 +1,15 @@
export { POST } from '@/app/api/openrouter/[...path]/route';
import { NextRequest } from 'next/server';
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jrf0110 I changed the gateway route from a re-export to a thin wrapper that clones the request with X-KILOCODE-FEATURE: direct-gateway before forwarding to the openrouter handler. This lets us positively identify external gateway consumers in microdollar_usage without depending on them to send any headers. Tested locally and confirmed the feature value is stored correctly. Any concerns with this approach or the request cloning pattern?

Image

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not so sure about relying on that the route. There are currently direct-gateway users that use the openrouter endpoint

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jrf0110 Thanks for flagging. Is it actually intended behavior or is that just where we ended up?
If external consumers can hit both /api/gateway/ and /api/openrouter/, we lose a clean way to identify direct gateway traffic. Ideally, each feature should have its own positive signal so we don’t end up with misattribution or silent failure modes.
Can we enforce separation moving forward? If not, what’s the right way to make direct gateway traffic reliably identifiable without relying on a catch all fallback from excluding all other known internal features signals?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it actually intended behavior or is that just where we ended up?

I suppose it's both. I think we never should have had our gateway route at /api/openrouter, but that's the way it was. Clients (Kilo or otherwise) hit the gateway all the same. IMO kilo feature clients should hit the gateway with some identifying header (e.g. x-kilocode-feature). Of course this is spoofable, so it should only be used for reporting purposes

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree we should not introduce behavioral differences between the endpoints (and we should get rid of the poorly named openrouter endpoint eventually).

Copy link
Contributor Author

@pedroheyerdahl pedroheyerdahl Feb 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

kilo feature clients should hit the gateway with some identifying header

That's exactly what this PR along with others in kilo and kilocode repo is doing. The open question is specifically about direct-gateway. It's a feature that needs a positive signal just like the others. Since we can't rely on external consumers to send the header, how should we identify their traffic? I'd appreciate your help defining that so we don't end up using "no header" as a catch-all, which would mask tracking issues.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reverted the wrapper. will create an issue for direct-gateway usage attribution to unblock the current work

Pedro Heyerdahl added 6 commits February 18, 2026 14:08
Add `feature` column to `microdollar_usage` to track which product
generates each token usage record. Every caller sends an
`X-KILOCODE-FEATURE` header validated against an allow-list.

- Create `src/lib/feature-detection.ts` with FEATURE_VALUES allow-list,
  FEATURE_HEADER constant, and validateFeatureHeader() function
- Add nullable `feature` text column to microdollar_usage with partial
  index on non-null values
- Wire feature extraction into openrouter and FIM gateway routes
- Replace gateway re-export with wrapper that injects `direct-gateway`
- Add `feature` field to ProxiedChatCompletionRequest and set it in
  security-agent and slack-bot callers
- Pass KILOCODE_FEATURE env var through cloud-agent server-manager
- Update processUsage pipeline, schema, view, migration, and all test
  helpers
- Add implementation plan in plans/microdollar-feature-tracking.md
Migrate the `feature` text column from `microdollar_usage` core table to a
dedicated `feature` lookup table with a `feature_id` foreign key on
`microdollar_usage_metadata`, consistent with other normalized columns
(editor_name, finish_reason, etc.).

- Create `feature` lookup table with unique index
- Add `feature_id` to `microdollar_usage_metadata`
- Remove `feature` column and its partial index from `microdollar_usage`
- Update view to join through the new lookup table
- Update insert logic to use upsert CTE pattern for feature
@pedroheyerdahl pedroheyerdahl force-pushed the feat/usage-feature-attribution branch 2 times, most recently from 5c02e0a to 08c7e7b Compare February 18, 2026 18:47
…t gateway route

- Replace manual Set-based feature validation with zod schema parsing
- Revert gateway route to a re-export
- Update tracking plan: remove DEFAULT_FEATURE, add CLI entry point
  default logic (cli vs unknown for kilo serve), clarify feature coverage
@pedroheyerdahl pedroheyerdahl merged commit 29d5cc6 into main Feb 18, 2026
12 checks passed
@pedroheyerdahl pedroheyerdahl deleted the feat/usage-feature-attribution branch February 18, 2026 19:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

Comments