Signed invocation token and check in the adapter¶
Context and Problem Statement¶
The server decides whether a person may trigger an agent (agent.invoke, I2). The adapter
outside the server then starts a runtime with real costs and side effects. How does the server
prove this decision to the adapter so that the adapter starts nothing without a valid decision,
double starts are impossible and the adapter can check whether its backend matches the configured
mode (I3)?
Decision Drivers¶
- The adapter must not do anything without a valid decision (mandatory test from 07).
- The decision must apply at the time of delivery, not at the time of the message (revoking permissions takes effect immediately).
- The adapter must be able to compare the configured mode and the reported mode of the backend policy (I3).
- An adapter must not be able to forge a decision, not even with its own token.
- Key rotation without downtime.
Considered Options¶
- Asymmetrically signed JWT (JWS, EdDSA) per delivery, keys via JWKS, check in the adapter and again at run start.
- HMAC verification tag with a shared secret per agent.
- No token; the adapter trusts the authenticated gateway connection.
- The adapter checks back with the server before every start ("may I?"), without a token.
Decision Outcome¶
Chosen: option 1, because only it combines forgery resistance toward the adapter, a verifiable
proof with all fields for I3 and key rotation without a shared secret. The token is issued anew
with every delivery (R-SY-07); the server checks it again at run start (jti, expiry, configured
mode equals reported mode).
Consequences¶
- Good, because an adapter token alone cannot start a run.
- Good, because the claim
billing_modeenforces the comparison of configured mode against reported mode at the location of the runtime. - Good, because an emergency key rotation delays at most one delivery: the next delivery carries a token from the new key.
- Bad, because every adapter must verify JOSE correctly (algorithm allow-list,
typ, time window). The SDK provides the check ready-made (ADR 0026). - Bad, because the clocks of server and adapter must roughly agree (tolerance as a configuration value).
Confirmation¶
- Scenario AG-08 in docs/spec/09-agents.md: a missing token, a wrong signature, a different
alg, a foreignaud, a wrongtyplead to no start. - Scenario AG-28: An expired, still current token does not lead to an abort; the adapter
renews it with
run.token.renew, the server decides anew in doing so, and the run starts exactly once (D-W-15). - Scenarios AG-31 and AG-32: A consumed start token resumes a waiting run neither through
run.token.renewnor throughresumeRun; a lost response ofrun.token.renewdoes not leave the run hanging, and it starts exactly once (D-W-44). - Scenario AG-22: scheduled rotation without failed runs, emergency with at most one delayed delivery.
- Scenario AG-09: a duplicate delivery leads to exactly one successful
run.start. - A schema test keeps the claims in line with docs/spec/05-actions.md section 5.
- M2-06: the schema test
TestInvocationClaims_MatchSpec(internal/runs/domain) holds the claim set of the code equal to 05 section 5 and 09 4.5; the reference verifier (internal/runs/adapters/runstest,TestReferenceVerifier_RejectsAlgAndTyp,TestReferenceVerifier_Points,TestReferenceVerifier_JWKSCache) and the server verifier (TestVerify_Reasons,TestInvocationToken_ForeignPurposeKey) reject every otheralg,typand key purpose; AG-22 at token level:TestAG22_ScheduledRotationFailsNoTokenandTestAG22_EmergencyRotationTokenLevel(test/integration/invocationtoken); the run level is M2-31's.
Pros and Cons of the Options¶
Option 1: Asymmetric JWT per delivery¶
- Good, because the adapter can verify but not forge (R-SY-08).
- Good, because standard libraries exist and RFC 8725 defines the rules (explicit
typ, algorithm allow-list; https://www.rfc-editor.org/rfc/rfc8725.html, retrieved 2026-10-10, [verified]). - Neutral, because the JWKS must be published and cached.
- Bad, because signing per delivery costs computing time (EdDSA is fast for this).
Option 2: HMAC with a shared secret¶
- Good, because simple and fast.
- Bad, because whoever can verify can also forge: a compromised adapter could issue tokens to itself.
- Bad, because the secret must be distributed and rotated per agent.
Option 3: Trust in the connection¶
- Good, because no additional mechanism is needed.
- Bad, because the adapter has no proof of the configured mode, the triggering person and the expiry; I3 would not be verifiable.
- Bad, because a delivery through another path (REST fetch, webhook) would prove nothing.
Option 4: Check back before every start¶
- Good, because the decision is always completely fresh.
- Bad, because it needs an additional synchronous call per run and blocks the start during network problems.
- Bad, because the adapter can trust the result only through its connection, so, like option 3, it has no independent proof.
More Information¶
- Baseline I2, I3; docs/spec/04-sync-protocol.md section 15.3; docs/spec/05-actions.md section 5; docs/spec/09-agents.md section 4.5.
- The claims including
space_id,trigger_kind,command_id,tool_classand the headertypare in docs/spec/05-actions.md section 5.