Story-2958: artifact contract + result delivery

Outcome. Ambient/result routes no longer hold an AgentCore invocation open while the brain thinks. They register an SDK async task, acknowledge immediately, fold their internal transient events into one strict agent_result_artifact.v1, and perform one create-only HTTPS PUT to the broker-minted exact-key URL. Live conversational routes remain generators.

Mental model. An event frame is a sentence in a live conversation: ordered, transient, and meaningful beside its neighbours. An artifact is the signed case file read months later. It therefore has its own identity, outcome, effects, receipts, totals, and trace. It does not copy agent_event_stream.v1's type/payload/seq envelope; every effect versions itself.

Lifecycle

broker → agent_invoke.v1 { input, result_delivery: { secret URL, explicit limits } }
brain  → validate + detach bearer block
       → app.add_async_task(...)                 # HealthyBusy BEFORE acknowledgement
       → asyncio.create_task(existing stream_invoke)
       → { run_id, status: "accepted" }           # /invocations closes

background:
  internal run.started / model.usage / effect / run.completed|failed
  → agent_result_artifact.v1
  → ONE HTTPS PUT, If-None-Match: *
  → finally: app.complete_async_task(task_id)     # success, failure, cancellation, 412

consumer (different repo, out of scope) → validate/persist → sender's own delivery rail

The pinned SDK's status timestamp is intentionally untouched. Focused proof calls repeated get_current_ping_status() while HealthyBusy and asserts _last_status_update_time stays fixed; only the Healthy↔HealthyBusy transition changes it. Quiescent pings therefore cannot starve the idle reaper.

Six runtimes, routes not brains

RuntimeRouteDeliveryDurable effect
Atlasdock_chatlive-rail (retired)—
Blake-registrar12 result modesartifactclosed registrar/document effect arms; deep-read candidates+rulings reassembled
Blake-registrarpause/resume/stop controlsexisting lifecycle generator— (controls are not result routes)
Vega-framerbuildartifactframe_output.v1
Mira-counselconverse, map_commandlive-rail generators—
Mira-counselcommandartifact declared; PLANNED — not built: W4 cutover— (cutover out of scope)
Indigo-readerreadartifactdocument.understanding.v1
context-judgecompactartifactmemory.folded.v1

Security properties

Receipts

Every observed model.usage becomes one artifact receipt, including usage emitted before a failed terminal event. Sequence is assigned once over the final run-wide list: 1..N. A missing provider generation id remains null; the stable local identity is <provider>:<run_id>:seq:<N>. Provider actual cost stays actual, while the independent static-table estimate is attempted on every generation (including OpenRouter) so drift remains observable for every governed model. Receipts stamp rate_card_version and the governed FX card's updated value as fx_rate_version.

Red before green

Initial focused run:
  ModuleNotFoundError: agent_contracts.result_artifact
  ModuleNotFoundError: agents._shared.result_delivery
  ModuleNotFoundError: agents._shared.artifact_runtime
  → 3 collection errors (expected RED before implementation)

Baseline egress gate against the planted fixtures:
  boto3.client("s3") fixture                  → violation (existing blanket ban held)
  second Request(..., method="PUT") fixture   → [] + assertion failure (RED)

After implementation:
  focused contract/security/lifecycle/governance set → 125 passed
  includes planted S3 client + second-PUT fixtures, unknown schema, 412, URL redaction,
  contiguous receipt sequence, add-before-ack, finally completion, and route inventory

Contract gaps recorded, not invented

1. Limits: neither ticket nor RFD supplies numeric byte/effect ceilings. The contract therefore requires positive limits in each result_delivery block and enforces those exact caller values. It does not reuse the unrelated 32-KB inline-stream threshold.

2. Failure authority: this repo's event modules say the broker owns authoritative failure_envelope.v1 stamping (IDs, timestamps, retryability, user copy). Copying that schema here would silently transfer authority. A failed artifact therefore carries the closed brain_failure.v1 facts the brain can honestly author; the out-of-scope consumer must stamp the authoritative failure envelope.

3. Anomaly persistence: unknown artifact versions are refused with a typed anomaly here. Recording that anomaly durably belongs to the out-of-scope broker/consumer because brains write no audit/run truth.

4. Declaration is not cutover: Mira's manifest declares command=artifact, but all three intents retain the live JSONL generator. PLANNED — not built: the W4 command background cutover, after the broker presign and result consumer exist. The shared background machinery lands here for those later callers.

5. Unknown model pricing: every observed generation enters the estimate computation, including provider-actual/OpenRouter generations. If a future model is absent from the governed rate card, the estimate remains explicitly null; the brain does not fabricate zero cost or disguise an estimate as actual. The shipped route names are covered by the rate-card resolution tests.

Verification

uv run ruff check                          → clean
root governance suite                      → 347 passed
Atlas suite (agent dir)                    → 512 passed
Vega-framer suite (agent dir)              → 164 passed
Mira-counsel suite (agent dir)             → 248 passed, 1 current-main failure (below)
Blake-registrar suite (agent dir)          → 515 passed
Indigo-reader suite (agent dir)            → 326 passed
context-judge suite (agent dir)            → 57 passed
brain-egress standalone gate               → clean
Story walkthrough gate                     → clean
Current-main Mira gate: after rebasing onto origin/main, the Mira suite is 248 passed / 1 failed. The failing discovery assertion says there are exactly seven suites, while the same origin/main already contains evals/conversational/channel_adversarial.py as an eighth suite. This Story changes neither file; the unrelated baseline repair is intentionally not folded into Story-2958.

Not proved in this repo: a live presigned request must include these exact headers in its signature; broker/consumer/sweeper, IAM signing role, S3 notification, and staging end-to-end delivery are explicitly outside Story-2958. A green local suite is not a live-rail claim.