feat: async operator human-gate contract — fire-and-park prompt + derived request ids (#1402)#1635
Conversation
|
Resolve by running |
|
Validated via One finding, verified against the code rather than inferred.
|
| Category | Status | Notes |
|---|---|---|
| Base branch | ✅ | dev |
| PR size | ✅ | 10 files (+350/−37) |
| Issue link | ✅ | Fixes #1402 — resolves, P2/type-feature/theme-reliability. Same-repo Fixes on a dev PR closes at the dev→main cut, per the documented SDLC |
| Commit messages | ✅ | conventional; the single commit explains why |
| Requirements | ✅ | scheduling.md §10.13 — cap semantics, rationale, cap=0 note, still-blocking list |
| Architecture | ➖ | no endpoint/schema/component change; architecture.md documents prompt plumbing (runtime-awareness), not prompt text |
| Feature flow | ✅ | operating-room.md updated + index row |
| Feature-flow format | ✅ | matches house style |
| Security sweep | ✅ | no keys, tokens, real emails, public IPs, .env, credential files |
| Infrastructure | ➖ | none touched |
| Build/config packaging | ➖ | no new top-level module, no new os.getenv() |
| Code quality | ✅ | text-only, verified; scope matches the stated purpose |
| Tests | ✅ | 17/17; sentinels across all 4 runtime builds, negative assertion on the old wording, cross-runtime section parity, task-mode carve-out, composed end-to-end, synced-copy parity |
Recommendation
REQUEST CHANGES — for one line:
- Either wire
db.mark_expired()into a sweep, or reword theexpires_atguidance (and its two doc echoes) so the contract doesn't promise platform behaviour that doesn't exist.
Everything else is ready. If you take option 2, I'd file the sweep as a follow-up next to #1632, since the never-expiring pending rows and the missing create caps are the same unbounded-growth story.
…t, derived request ids, cap semantics (#1402) The MAX_REDELIVERY cap + poison-park mechanism shipped in Phase 3 (#1550); this lands the remaining #1402 deliverable: the authored contract agents receive. - Platform prompt (PLATFORM_INSTRUCTIONS → Operator Communication): rewrite around fire-and-park / never block-and-wait (park → end the turn → process responded items in a later turn), ask-before-irreversible-actions guidance, execution-id-derived request ids (approval-{execution_id}-{slug} — global-PK collision safety + re-delivery dedup), expires_at + resume-instructions guidance for agents with no next turn. Section stays MCP-tool-name-free so the Codex transform can't fork it. - _mode_guidance task mode: explicit park carve-out so "execute to completion, don't ask questions" no longer contradicts the gate on the exact autonomous path it targets. - Synced the dead-but-present reference copy (config/trinity-meta-prompt/ prompt.md, injection removed in #136) and added the agent-guide async-comms section (incl. honor-system framing). - Docs: requirements/scheduling.md §10.13 (raw-attempts cap semantics + still- blocking list for pull default-on), TARGET_ARCHITECTURE.md stale claims fixed ("all unbuilt today" → shipped split), operating-room.md (async contract, poison-park items, removed-#136 prompt-integration correction), learnings.md (prompt-delivery-path pitfall). - Tests: tests/unit/test_1402_prompt_contract.py — sentinel phrases across every runtime build, old block-inviting wording gone, section byte-identity across runtimes, carve-out, meta-prompt/guide sync (17 tests). Residual work tracked: #1629 (pull-path prompt delivery + re-delivery banner), create caps). Fixes #1402 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
obasilakis
left a comment
There was a problem hiding this comment.
Checked the contract against the code rather than just the prose, and it holds up:
expires_at → expiredis real —mark_expiredruns in the 5s sync loop (operator_queue_service.py:106), so the prompt isn't promising behaviour the platform lacks.execution_idis populated and rendered on the autonomous path (task_execution_service.py:1170→platform_prompt_service.py:525), so the derived-id instruction resolves.- The collision rationale is well-founded:
operator_queue.idisColumn("id", Text, primary_key=True)andcreate_itemdoes.on_conflict_do_nothing(index_elements=["id"])— a colliding id genuinely is swallowed silently. Fixes #1402is legitimate: the #1550 mechanism (MAX_REDELIVERY,redelivery_count, alert-first poison-park) is on dev inlease_reaper_service.py, so this contract surface completes the ACs.
Tests: 17 passed, and still pass rebased onto current dev. One necessary change before merge.
The #1629 residual claims are stale — that fix merged to dev a minute before this PR opened
Three docs assert present-tense code state that is false on the merge target:
docs/memory/requirements/scheduling.md§10.13, in the "Still blocking pull default-on" list:pull-path platform-prompt delivery + re-delivery banner (#1629 — pull-claimed turns currently receive NO platform instructions at all)
docs/planning/TARGET_ARCHITECTURE.md:the pull-path delivery of that prompt (pull-claimed turns receive no platform instructions today) is #1629, still blocking default-on for effect-bearing agents
docs/memory/feature-flows/operating-room.md:Known gap (#1629): pull-claimed turns bypass platform-prompt composition entirely and currently receive none of these instructions
f6567b56 ("fix(pull): compose the platform system prompt on the pull-claim path + re-delivery banner", #1633) merged to dev at 14:00:26Z; this PR opened at 14:01:39Z. On dev, pull_coordination_service.py:87 calls compose_system_prompt(...) for pull-claimed turns, and #1633 shipped the re-delivery banner alongside it. Pure timing — the branch predates the fix by about a minute.
Citing #1629 as a tracked residual is still correct: it's status-in-dev, open only until the release cut. What needs changing is the present-tense code claims and its place in the still-blocking list. That list is the gate for defaulting pull mode on, so a resolved blocker sitting in it means the next reader either redoes #1629's work or mis-sequences the epic — which is exactly the kind of thing these docs exist to prevent.
Suggested wording: #1629 — fix merged to dev (#1633), pending release, and drop it from the still-blocking list. The remaining blockers there are unaffected (#548/#333 trace fidelity, #1401 prior_trace injection, fail-closed execution_id, #1632 queue caps).
Worth noting the irony: this PR's own learnings.md entry is precisely about the pull path not receiving the platform prompt. That lesson stands on its own — it's what caused #1629 to be filed and fixed. Only the "still open / never delivered" framing needs updating.
Rebase needed
The branch conflicts with dev on tests/registry.json only, and it's additive on both sides (dev's #1600/#1649/#1615 entries vs this PR's #1402 entry). I rebased locally to check: resolution is keep-both (80 dev entries + 1 = 81, no duplicate files), and the prompt + pull suites pass afterwards — 68 passed, 1 skipped.
Everything else looks good. The sentinel-lock approach, the cross-runtime byte-identity check, and the meta-prompt/agent-guide parity tests are a good fit for a contract that lives in prose.
…ry claims The branch predates #1633 by ~1 minute (merged to dev 14:00:26Z; this PR opened 14:01:39Z), so three docs assert a code state that is false on the merge target: - requirements/scheduling.md — #1629 listed under "Still blocking pull default-on" ("pull-claimed turns currently receive NO platform instructions at all") - TARGET_ARCHITECTURE.md — "receive no platform instructions today ... still blocking default-on", and #1629 in the residual list - feature-flows/operating-room.md — "Known gap (#1629): ... bypass platform-prompt composition entirely" On dev, pull_coordination_service.py composes the platform prompt on the claim path (fail-open) and injects the re-delivery banner. The still-blocking list gates the pull default-on decision, so a resolved blocker sitting in it costs a future reader real work. #1629 stays referenced (status-in-dev — open until the release cut), now described as shipped-pending-release rather than unbuilt. learnings.md is left as-is: it records why #1629 was filed, which remains accurate history.
48d6600 to
e2d182a
Compare
obasilakis
left a comment
There was a problem hiding this comment.
Resolving my own changes-request: I rebased the branch onto current dev and pushed the fix myself (e2d182a).
Rebase — only tests/registry.json conflicted, additive on both sides. Resolution is keep-both: dev's 80 entries + this PR's #1402 entry = 81, no duplicate files. The feature commit is unchanged at +350/−37.
The fix (docs only) — corrected the three stale #1629 claims to say shipped-pending-release instead of unbuilt, and moved it out of the "Still blocking pull default-on" list in both scheduling.md and TARGET_ARCHITECTURE.md. #1629 stays referenced (it's status-in-dev, open until the release cut). learnings.md I deliberately left untouched — it records why #1629 was filed, which is accurate history and still the durable lesson.
Verified after the rebase: test_1402_prompt_contract.py + test_platform_prompt_runtime.py + test_1081_pull_endpoints.py → 68 passed, 1 skipped.
Since I both requested and made this change, please sanity-check my wording on the blocker lists before merging — I'd rather you disagree with the phrasing now than inherit it. Approving on the substance: the contract is well-founded (I verified expires_at → expired fires, execution_id resolves, and the global-PK collision is real), and the sentinel-lock + cross-runtime byte-identity tests are a good fit for a contract that lives in prose.
Summary
respondeditems in a later turn; ask-before-irreversible-actions guidance; execution-id-derived request ids (approval-{execution_id}-{slug}) closing the global-PK collision trap and making re-parks under re-delivery idempotent;expires_at+ resume-instructions guidance for agents with no future turn._mode_guidancetask mode gains an explicit park carve-out — "execute to completion, do not ask questions" no longer contradicts the gate on the exact autonomous path it governs.config/trinity-meta-prompt/prompt.md(dead reference copy, injection removed in refactor: Runtime injection of platform instructions via --append-system-prompt #136) + new agent-guide async-comms section (incl. explicit honor-system framing — this is a compliance contract and audit trail, not a security boundary).requirements/scheduling.md§10.13 (raw-attempts / no-reset-on-progress cap semantics + rationale, cap=0 design note, the still-blocking list for pull default-on),TARGET_ARCHITECTURE.mdstale claims fixed (cap was described as "all unbuilt today"),operating-room.md(async contract, poison-park item type, refactor: Runtime injection of platform instructions via --append-system-prompt #136 prompt-integration correction),learnings.md(prompt-delivery-path pitfall).Changes
src/backend/services/platform_prompt_service.py— text-only:PLATFORM_INSTRUCTIONSOperator Communication rewrite +_mode_guidancecarve-outconfig/trinity-meta-prompt/prompt.md,docs/TRINITY_COMPATIBLE_AGENT_GUIDE.md— synced contract copiesdocs/memory/requirements/scheduling.md,docs/planning/TARGET_ARCHITECTURE.md,docs/memory/feature-flows/operating-room.md,docs/memory/feature-flows.md,docs/memory/learnings.mdtests/unit/test_1402_prompt_contract.py(+ registry) — sentinel phrases in every runtime build, old block-inviting wording gone, Operator Communication section byte-identical across runtimes (MCP-name-free), task-mode carve-out, composed end-to-end prompt, synced-copy parityResidual (filed, referenced from the docs)
operator_queue.idglobal-PK cross-agent collision (schema scoping)Test Plan
pytest tests/unit/test_1402_prompt_contract.py— 17 passedpytest tests/unit/test_platform_prompt_runtime.py tests/test_platform_prompt_unit.py tests/unit/test_public_channel_prompt.py— all passing (no assertions on the changed wording elsewhere; repo-wide grep confirmed the only other old-wording copy is a historical 2026-03 spec, left per point-in-time convention)Fixes #1402
🤖 Generated with Claude Code