Skip to content

backend-agents C14d: durable running marker + full route matrix/build gate #833

Description

@btipling

Parent: #794
Umbrella: C14 — #808
Label: backend-agents
Status: HANDOFF-READY (plan-review 2026-08-24; one-turn sub-issue of the C14 split)
Blocked on: #835 (C14b start+stream)

Part of breaking C14 (POST /api/turns) into small single-turn, one-issue/one-PR rows. C14d is the third row: make the started run durable (post-start running marker + warning contract) and land the full C14 route test matrix + next build deploy-gate. One issue → one PR → one adversarial review → one merge → close. Do not expand scope.

Slice

Two parts, both small and local to the C14b route + the B8 overlay seam:

1. Durable running PATCH (the missing "real start surface" piece)

The pre-return PATCH persists turnRunId=run.runId + turnStatus='running' into the session envelope after start() returns (you cannot persist the id before start returns it). Reuse the existing B8 seam overlayWorkerMeta (lib/agent/workerMetaOverlay.ts:164, supports turnStatus:'running', copy-forward preserves host siblings byte-for-byte, LWW with a strictly-newer clock) — a separate write from the terminal B13 seam (which writes completed); do not reuse the terminal seam for the running marker. Same construction pattern as createTurnPersistSeam.

Post-start persist-failure contract (locked): if the running PATCH fails AFTER start() succeeded, the run IS started — do not 500. Return the stream/JSON {runId} and surface the persist failure as a warning (x-workflow-run-warning header or warning field). Durable truth is getRun/C16 (attach reconciles).

2. Full C14 route test matrix + deploy build gate

app/api/turns/route.test.ts — the complete 8-row matrix (including C14b's rows, now asserted against the durable route): 400 guard · start called + runId · 503 on start throw · running-PATCH-fails-after-start → non-500 + {runId} + warning (run considered started) · SSE contract · JSON {runId} · auth gate · in-workflow runId equality. Plus next build exit 0 as the mandatory deploy gate for the Workflows-bundle layer (the umbrella: green tests never substitute for the build/review).

Architecture lock (from #794 / C14 #808 — copy, do not weaken)

  • One run = one prompt; one step = one model round / one tool / one persist (unchanged).
  • turnRunId = Workflow run.runId, never sessionId.
  • Terminal turnStatus='completed' (B13 seam) stays distinct from the running PATCH (B8) — two separate writes, ordered by the strictly-newer LWW clock so they never self-conflict.
  • 'use workflow' file must not statically import db/mcp/blob/node:crypto/node:dns; the B11 real-entry closure regression still walks zero banned modules.

DoD

  • Post-start running PATCH via B8 overlayWorkerMeta (turnRunId=run.runId + turnStatus='running', separate from terminal seam), immediately after start() returns, before the route returns the stream/{runId}
  • Running-PATCH failure after start → non-500, still returns {runId} + warning header/field (attach reconciles); no /api/agent fallback
  • Full 8-row route matrix landed in app/api/turns/route.test.ts (400 · start+runId · 503 · running-PATCH-fail non-500 · SSE · JSON · auth · runId-equality)
  • typecheck + full direct vitest run + next build (Workflows-bundle) green
  • Caps: no cap added OR changed (reuses C14b's 1800 constant)

Testing matrix (complete — app/api/turns/route.test.ts)

# Case
1 Authorized; valid sessionId+promptstart called, run.runId PATCHed 'running', JSON {runId} + header
2 Missing sessionId400
3 start throws → 503 fail-closed, no fallback
4 running PATCH fails after start → non-500, still {runId} + warning, run considered started
5 Accept: text/event-streamcontent-type: AGENT_STREAM_CONTENT_TYPE, body is run.getReadable(), header set
6 Accept: application/json → JSON {runId}
7 Unauthorized → auth gate 401/403 before any persist/start
8 In-workflow terminal persist uses getWorkflowMetadata().workflowRunId, equals route-side run.runId (spy the terminal seam arg)

Cloud ops

N/A. Same request-scoped route as C14b — no Production data/secret/env mutate, no GHA, no cutover. Durable-turn ops documentation lands in H23–H25 (deferred).

Living docs

N/A for this row. H23–H25 (docs rows) cover durable-turn ownership/wire; C14d adds no user/operator rule a doc must describe (attach/cancel are later rows C16/G22).

Caps

Cap Value Status Rationale
maxDuration 1800 s Reused constant (C14b) #808 caps lock. No change → no human gate.
MAX_WORKFLOW_STEPS 256 Existing (B12) Not reopened.

Risks & mitigations

  • Running vs terminal PATCH self-conflict: B8 strictly-newer overlayClock (B13) keeps the running write (earlier clock) from being clobbered by a stale terminal overlay on a real run (locked; LWW conflict test).
  • Persist failure post-start: never 500 — warning + attach-reconciles (locked).
  • Deploy regressions in the bundle layer: next build exit 0 is a mandatory DoD row, not optional.

Refs

Metadata

Metadata

Assignees

No one assigned

    Labels

    backend-agentsClient-independent agent turns — backend-run, survive tab close / refreshenhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions