feat(agentic-server): carry invocation/job/attempt/run linkage into InferenceEntry - #1812
Conversation
…nferenceEntry; strip correlation headers on public servers
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
|
Review complete. 🟡 1 medium 💬 Inline comments (1)
The agentic-server gateway now reads task-correlation headers (
Reviewed commit: ae65bd8 |
There was a problem hiding this comment.
Adds task-correlation header reading to the agentic-server gateway so inference entries can be attributed to a task, but the metering producer never forwards those headers, leaving the feature inert end-to-end.
Key findings
- 🟡 Correlation headers never forwarded by the metering runtime — router.ts:71
…tion-Id/X-Job-Id/X-Attempt on MeteredIdentity, X-Run-Id threaded from composeRun
Summary
Companion to constructive-db Phase 3 of the billing plan (constructive-planning#1985): inference is COGS attributed to the task the customer paid for, never a customer meter of its own. For that join to exist, both ends of the gateway have to carry which task each model call belongs to.
Gateway (
agentic-server)InferenceEntrygainsinvocationId?,jobId?,attempt?,runId?; newInferenceAttribution = Pick<InferenceEntry, 'databaseId'|'entityId'|'actorId'|…linkage>exported.readAttribution(req)readsX-Database-Id / X-Entity-Id / X-Actor-Id / X-Invocation-Id / X-Job-Id / X-Attempt / X-Run-Idonce per request; everysink.logInference({...})call (chat ok/error/stream, embed ok/error,/v1/usage) spreads it, so no sink path can drop linkage.X-Attemptmust be^\d+$or is dropped.IDENTITY_HEADERS, so a public (isPublic: true) server strips them exactly like identity — an external client cannot pin its usage onto someone else's invocation.Client (
@agentic-kit/metering+@agentic-kit/pi) — the producer side the reviewer flagged as inertMeteredIdentitygains the same four optional fields;buildIdentityHeadersemits them (throws up front on a non-digitjobIdor non-integer/negativeattempt, same posture as the existingdatabaseIdcheck). Both the gateway lane and the self-report lane (reporter.ts) go through this one function.composeRunthreads itsrunIdinto the metering identity:identity: { runId: options.runId, ...identity }— so every pi run now sendsX-Run-Id, while a host may still pin it explicitly. Hosts that dispatch from a platform invocation passinvocationId/jobId/attempton the identity.Wire format is unchanged for callers that don't send the headers (all fields optional). The constructive-db runtimes send
X-Invocation-Id/X-Job-Id/X-Attemptfrom the job frame.Tests: agentic-server gateway 22/22, metering 38/38, pi 62/62 — linkage forwarded end to end, malformed/absent linkage stays undefined or is refused client-side, public strip.
Link to Devin session: https://app.devin.ai/sessions/bc06c04115054722b1913a1bcfd99256
Open in Devin Desktop: https://app.devin.ai/desktop/session/bc06c04115054722b1913a1bcfd99256?variant=devin
Requested by: @pyramation