[Story-1221] Clerk worker — SQS→AgentCore→truth→WS push, idempotent claim + in-stream cost cap (ADR-110 T6+T14)

Story 1221Epic None8 files · +973/−4

PR #312 · pramaan-io/pramaan-functions · merged 2026-06-12

Why this matters

One SQS message, two clerks, one runaway bill.

Story twelve-twenty-one — ADR-110 T6 + T14

Double work. Runaway cost. No final word.

The fix — Claim once, cap the cost, file the disposition — the clerk never leaves a case open.

How it flows

sequenceDiagram
  autonumber
  participant sqs as SQS Inbox
  participant clerk as Clerk
  participant brain as AgentCore
  participant truth as Truth (DB)
  participant ws as WS Counter
  sqs->>clerk: case file arrives
  clerk->>truth: stamp 'claimed'
  clerk->>brain: invoke
  brain->>clerk: frames stream
  clerk->>ws: whisper frame
  ws-->>clerk: 410 Gone
  clerk->>truth: file disposition

runner.py · +459 — new methods

Methodin → outwhat it does
ClerkDeps(seams, session_factory, agentcore_client, agent_runtime_arn, push, gone_exceptions)
process_command(command, deps) → str
_set_scope(session, firm_id) → None

What changed, file by file

The clerk's core pipeline — claims the run idempotently, invokes the brain, streams frames to the WS counter, enforces the in-stream cost cap, and always files a terminal disposition.

new classsignature
ClerkDeps(seams, session_factory, agentcore_client, agent_runtime_arn, push, gone_exceptions)
process_command(command, deps) → str
_set_scope(session, firm_id) → None
_claim_run(command, deps) → bool
_append_event(deps, run_id, firm_id, event_type, payload) → None
_terminal_update(deps, run_id, firm_id, status, fields) → bool
_invoke_brain(command, deps) → Any
_FramePusher(class)
push(frame_type, payload) → None
_consume_stream(body, command, deps, pusher) → str
_abort_cost(deps, run_id, firm_id, cost_usd, totals, model_id, pusher) → None
_fail_run(deps, run_id, firm_id, error, pusher) → None
New fields: model_id : str | None

Lambda entry point that builds ClerkDeps — AgentCore client, WS push, and the gone-exception list — and dispatches the SQS command into the runner.

new methodsignature
_agentcore_client() → Any
_ws_management_client() → Any
_push(connection_id, frame) → None
_gone_exceptions() → tuple[type[Exception], ...]
_build_deps() → ClerkDeps
lambda_handler(event, context) → dict[str, Any]

Migration adding the three intake-scope columns to agent_runs so the clerk can scope every run back to its matter, chat session, or form.

new methodsignature
upgrade() → None
downgrade() → None
New fields: revision : strdown_revision : str | Sequence[str] | Nonebranch_labels : str | Sequence[str] | Nonedepends_on : str | Sequence[str] | None

Adds matter_id, chat_session_id, and form_id to the agent_runs model so the clerk can record and query which intake a run belongs to.

New fields: matter_id : UUID | Nonechat_session_id : UUID | Noneform_id : str | None

Files changed (8)

File±
alembic/versions/c7d8e9f01234_0092_agent_run_intake_scope.py ★+49
docs/walkthroughs/story-1221-walkthrough.html+57
src/clerk_worker/__init__.py+1
src/clerk_worker/handler.py ★+105
src/clerk_worker/runner.py ★+459
src/shared/agents_db_models.py+9 −4
tests/unit/clerk_worker/__init__.py+0
tests/unit/clerk_worker/test_runner.py+293