Skip to content

M2-09 Start, renewal, end and cancellation of runs, orphaned runs

No Date Ruling Reason
D-M2-09-0 2026-10-22 Built on the old integration line and ported to main after M2-08 landed: only the ticket's own commits were brought over, with the integration fixes INT-W54/4 (lint findings, no behaviour change) and INT-W54/5 (the canary scenario calls the six enabled operations). The spec commit that ported run.token.renew and renewInvocationToken into the old base is dropped, because main already carries both; the additions to the German chapter files of the old base go to their English files on main. INT-W54/3 is not needed, because the terminal sweep of M2-46 is not on main; AccessChanged and AgentDisabled -> runs follow MessagePosted -> runs directly. The rules of the landing replace the integration branches; main is the reference.
D-M2-09-1 2026-10-21 OwnerID of a run descriptor: runs.agent_id for run.start, run.token.renew, run.finish; runs.triggered_by for run.cancel. An unknown run answers the 404 of an invisible one. The principal each action reserves the run to (05).
D-M2-09-2 2026-10-21 runs computes the token verdict (authorize.TokenFacts); authz decides run.start as All(AgentSelf, Token(valid)), run.token.renew as All(AgentSelf, Token(valid, expired)); the guard of run.start gives billing_mode_mismatch, then tool_class_mismatch. The authz copy is InvocationFacts (TokenFacts serves token.create). runs owns keys and jti columns; the reasons are decisions of authorize (05 1.1, 07 4.1).
D-M2-09-3 2026-10-21 Decision.Detail carries the problem reason (expired, superseded, invalid) of invocation_token_invalid; the audit keeps that code with detail token_verdict. One audit code, three adapter reactions.
D-M2-09-4 2026-10-21 Server expiry tolerance at start is auth.clock_leeway; exactly at exp plus it the token is valid. Upper bound of the adapter's tolerance (09 4.5).
D-M2-09-5 2026-10-21 startRun on waiting_input and resumeRun on queued answer 409 invalid_state_transition. Each endpoint performs only its own transition (06 16).
D-M2-09-6 2026-10-21 RunStart.external_session_ref is refused with 400 validation_failed (errors[].code not_enabled) until M2-12. The session row it binds is M2-12's (06 5 point 3).
D-M2-09-7 2026-10-21 A token rejected as invalid changes nothing; the adapter fails the run with invalid_invocation_token. 04 15.3 point 4; a forged token must not fail a foreign run.
D-M2-09-8 2026-10-21 A renewal refused at step 4 fails the run with the reason of the delivery check, audits it (stage renewal) and answers 403 agent_invocation_denied with that reason. The adapter sees the same cause as in run.state_changed.
D-M2-09-9 2026-10-21 finishRun: failed and cancelled need reason; failed keeps adapter reasons of 09 5.2, others become adapter_error; cancelled takes only cancel-request reasons; completed ignores it. An adapter confirms the server's cause and cannot invent one.
D-M2-09-10 2026-10-21 An adapter-reported I3 mismatch writes one audit entry run.finish deny with billing_mode_expected and tool_class codes and counts bothy.agent.billing_mode_mismatch. 10 14 row "I3 mismatch".
D-M2-09-11 2026-10-21 RunCancel.reason is checked (textcheck.DisplayText, audit.reason_max_chars) and stored nowhere. 03 allows free text only for mandatory admin reasons (spec gap).
D-M2-09-12 2026-10-21 cancelRun on queued ends the run user_cancelled at once and still writes run.cancel_requested; no job. No delivery marker in 03; a later start gets 409 already_started.
D-M2-09-13 2026-10-21 run.cancel_requested is personal to the run's agent (agent.events.consume), resource without version; requested_by only for user_cancelled. Only a person's stop has a requester (05 6).
D-M2-09-14 2026-10-21 RunFinish.costs is refused with 400 validation_failed (errors[].code not_enabled) until M2-22. Dropping reported costs silently would lose money data.
D-M2-09-15 2026-10-21 Reason after a loss of trigger: user.disable user_disabled (all runs of the person), channel.archive/space.archive channel_read_only, else access_revoked. 08 11 point 7, 09 5.2.
D-M2-09-16 2026-10-21 After a loss of trigger the states stay (also queued): cancel request plus runs.cancel_timeout. Deliveries and renewals fail on their own; a token cannot outlive agent.cancel_grace.
D-M2-09-17 2026-10-21 runs.cancel_timeout is scheduled per request at now plus agent.cancel_grace (args the run); it ends an active run cancel_timeout, an ended one stays. 03 keeps no time of a request; repeats are harmless.
D-M2-09-18 2026-10-21 A disabled agent's runs end agent_disabled at once with run.cancel_requested; no job. Its tokens are revoked in the same transaction (09 2.1).
D-M2-09-19 2026-10-21 runs.stall_check runs every agent.presence_timeout (queue maintenance, once at worker start). The stall warning of run.stall_after is not built (no time of the last run.update).
D-M2-09-20 2026-10-21 interrupted uses the last persisted heartbeat (run missing from active_runs, heartbeat more than agent.presence_timeout after started_at); without active_runs no interrupted. The worker sees only the stored heartbeat (03 agents).
D-M2-09-21 2026-10-21 Start check: agent.heartbeat_persist_interval below run.orphan_timeout. The stored heartbeat lags by the persist interval.
D-M2-09-22 2026-10-21 Names follow the code: port methods read the principal from the context; subscribers are methods of runs/app.Acceptor; domain LiveRun; port agents.Heartbeats with agents/app.HeartbeatReader. Brief names predate the merge (Boundaries).
D-M2-09-23 2026-10-21 Tokens of trigger_kind interaction are not renewed (internal error) until M2-15. Since M2-16 is on main, a token of a command run is renewed as the agent.invocation of that run with the command of the token (renewalInput, the resolver of command runs); a test holds every run trigger kind the delivery check resolves renewable (TestRenewalInput_CommandRun, 2026-10-22, red team finding of the rebase). The delivery check has no resolver for interaction yet; I2 holds. A command run must not hang in queued until run.queued_max_age because its token expired before the start.
D-M2-09-24 2026-10-21 runs.stall_check reads all started active runs without a batch limit. At most one per thread (R-DM-14); no number without a key.
D-M2-09-26 2026-10-22 runs.stall_check reads the persisted heartbeat of the run's agent again inside the unit of work that locked the run, and decides the verdict on that reading; the first read only selects the candidates. The examples of the 200 responses of startRun and resumeRun show a run in running. Review finding on the pull request: a heartbeat stored between the first read and the lock must win, as the comment and 09 5.5 say (TestStallCheck_HeartbeatAfterReadWins); a start answers the started run.