Story-2981: the broker presigns one key, writes the pending row, and returns

Why this story exists

Until now the Lambda broker held the AgentCore invoke stream for the whole run: it folded SSE frames while the brain thought, and a botocore read timeout or a stalled socket turned a healthy 28-minute read into a ReadTimeoutError, a redelivered record, and a fresh full run the firm paid for twice.

This story is the moment the Lambda stops waiting (RFD-AGT-014). For an artifact route the broker now does four things and goes home:

1. tx:  pipeline record pending
        + agents.agent_run (delivery_status='awaiting_artifact',
          expected_key, deadline_at, run_origin='system', trace context)
2. mint: assume the Chore-2978 signing role, run the D9 mint guard,
        presign ONE create-only PUT URL (expiry = budget + slack + write allowance)
3. invoke_agent_runtime(accept='application/json') with the
        result_delivery block stamped into the envelope
4. return. Hold nothing. (~2s — the brain keeps working)
The pending row is written BEFORE the invoke, in the pipeline record's own transaction. A row written after the invoke is lost exactly when the invoker dies between the two. The planted negative test kills the broker between the write and the invoke and proves the sweeper's one indexed predicate (delivery_status='awaiting_artifact' AND deadline_at < now()) still finds the run.

One key, create-only, broker-stamped

The key is agent-runs/{firm_id}/{yyyymm}/{run_id}/{seat}/result.json — the whole string composed by the broker, {yyyymm} from the broker clock. The brain composes no part of it; the presigned URL, not any prefix-wide IAM grant, binds the brain to that exact key. The outcome (success | failure) is an in-envelope discriminator, not a second key. The brain's PUT carries If-None-Match: *, which the Chore-2929 bucket policy forces — a second write is refused by S3 with 412, and that 412 is the detector for the empty-key window (D21).

The mint-time guard (D9): refuse what cannot deliver

invoke_budget_s + delivery_slack_s + write_allowance_s
     <= REMAINING signing-credential lifetime AT MINT   ← never a nominal figure
     <= 3,600 s                                        ← the MEASURED role-chaining cap (Spike-2928)
Seatbudget + slack + write allowanceFits a fresh 1h session?
Registrar240 + 300 + 60 = 600syes
Deep Read700 + 700 + 60 = 1,460syes
Indigo840 + 840 + 60 = 1,740syes
The 8-hour ambition28,800 + 28,800 + 60 = 57,660srefused at startup, term named: invoke_budget_s

The guard reads the STS-returned Expiration of the assumed signing session — never the nominal 3,600s. A cached session that can no longer cover the run is refreshed with one re-assume; a run that cannot fit even a fresh session is refused with MintGuardRefusalError naming the term that blew. A nominal figure would make the same code succeed at 10:00 and fail at 10:07 — the Bug-2381 pattern in a new costume, and it is planted-broken tested.

The URL is a bearer secret (D18)

Three layers, each with a named check. (1) The broker never logs it — the census lines carry ids and outcomes only, and the no-log-line test asserts the URL, its signature, and even its redacted husk appear nowhere. (2) The log formatter redacts X-Amz-Signature / X-Amz-Credential / X-Amz-Security-Token values anywhere, as a backstop. (3) The block is stamped as a sibling of the envelope's input — the prompt-assembly assertion proves it can never ride input, history, or counsel_message into a model context (Indigo ingests adversarial documents daily).

One trace across the artifact hop (D8)

traceparent/tracestate are captured AT the pending-row write and stored on the row (migration 0216) so the sweeper can LINK terminated runs, and stamped into the envelope's meta so the brain echoes them into the artifact and the S3 object metadata — the results consumer (#2982) then parents its AGENT_RESULT_DELIVERED span from the same trace id. The two span names (functions.agent_result.delivered, functions.agent_run.swept) are registered in code per the ADR-124 amendment (Chore-2935) ahead of their first use. A dedicated test proves OTel baggage planted with matter/client identifiers never reaches the stamped carrier — ADR-124's "attribute, never baggage" rule, asserted for this hop.

The census (D13)

Every seam emits a structured agent_delivery.outcome line — a skip with no log is a design defect:

OutcomeSeam
budget_exceeds_ceilinga seat whose budget cannot fit the 3,600s ceiling
slack_below_write_allowanceD7 floor guard — a slack that would mass-fail healthy runs
mint_guard_refusedremaining credential lifetime at mint cannot cover the run
presign_failedthe signing role could not mint
invoke_failed_pending_row_visibleinvoke failed AFTER the pending row was written — the row is sweeper-visible
dispatchedthe healthy path

Proof

Recall

This story ships the mechanism only. The rail cutovers (Deep Read, registrar, understanding, Vega), the results consumer (#2982), and the sweeper (#2980) are their own tickets; conversational/WS rails are untouched and keep working. src/shared/agent_delivery is banned from ever holding a stream, and that ban is a test.