Skip to content

feat: add guarded primary provider switching and improve fleet coordination - #4

Merged
dscott98 merged 14 commits into
mainfrom
fm/firstmate-main-provider-switch-r6
Sep 21, 2026
Merged

dscott98 merged 14 commits into
mainfrom
fm/firstmate-main-provider-switch-r6

Conversation

@dscott98

Copy link
Copy Markdown
Owner

Intent

If usage quota gets low, only the main session may switch to OpenRouter 5.6 Sol; keep the workers on quota-plan providers and manage their timing and usage. If it can be figured out easily and quickly, build out the ability for the main session to switch providers. PRs and merges are authorized only in repositories owned by dscott98; none of this work should go upstream.

What Changed

  • Add /fm-openrouter-sol for manual main-session switching to OpenRouter GPT-5.6 Sol, guarded by primary-lock ownership, authentication, and an independent quota-plan supervision pin; support account-aware quota dispatch.
  • Add idempotent inbox capture, wake repair, durable replies, receipts, and readiness reporting; route worker-owned Lavish feedback through task inboxes with explicit round acknowledgement.
  • Support home-local worker brief additions, correct composer footer detection, bound run-inventory reads, parallelize contribution polling, and refuse merge monitoring for draft PRs.

Risk Assessment

✅ Low: The provider switch is restricted to the primary session, preserves quota-plan supervision and worker defaults, and the inbox fixes address the previously reported concurrency defects.

Testing

Four targeted test scripts and isolated Pi/inbox checks passed. Two scenarios have supported live passes; five remain untested under the live-validation contract. RPC and CLI evidence was captured; no rendered UI was exercised or screenshot captured. Temporary files were removed. Successful authenticated OpenRouter inference remains untested.

  • Live validation: ⚠️ inconclusive - 2 of 7 scenarios driven live against the product
Scenario Result Live Evidence
Main switches to OpenRouter while supervision and fresh workers retain quota-provider models ⏸️ untested no The prior payload recorded only integration checks using fixture credentials and loopback provider transport, not a live result. Authorized test credentials and live provider execution are needed to e…
Worker sessions, missing supervision pins, and missing credentials refuse the switch ✅ pass live live-guards.jsonl
Malformed pins, secondmate markers, extra arguments, and lost primary ownership cannot change model selection ⏸️ untested no The prior payload marked this non-live and recorded fixture model configuration; it did not establish a live result. This scenario needs execution against a live product configuration.
Worker dispatch respects account-specific quota and refuses exhausted or ambiguous choices ⏸️ untested no The prior payload recorded quota and resolver fixture checks only, not a live result. Live account quota and dispatch execution are needed to establish this scenario.
Concurrent inbox retries preserve one note and report acknowledgement after handling ✅ pass live inbox-live.jsonl; tests/fm-inbox.test.sh
Concurrent reply publication cannot advance receipts past an unseen reply ⏸️ untested no The prior payload recorded a controlled concurrency regression only, not a live result. Concurrent reply publication and receipt handling must be exercised against the live product.
Main completes an authenticated OpenRouter Sol turn after switching ⏸️ untested no No test OpenRouter credential was configured in the isolated environment. Provide an authorized test credential and rerun this scenario.
Evidence: Real Pi refusal responses

Source: Real Pi refusal responses

{"case": "worker", "event": {"type": "extension_ui_request", "id": "4e90ec63-09f9-4ce7-83c7-4403ff6ed4c9", "method": "notify", "message": "Provider switch unavailable: only the top-level Firstmate primary may switch", "notifyType": "error"}}
{"case": "missing-pin", "event": {"type": "extension_ui_request", "id": "82ed2d56-6c74-4535-a9ea-9846833c4d1f", "method": "notify", "message": "Provider switch unavailable: pin supervision to an independent openai-codex model with /supervision-model first", "notifyType": "error"}}
{"case": "missing-auth", "event": {"type": "extension_ui_request", "id": "aa830cb2-da66-413b-9f8f-f4275959fdf5", "method": "notify", "message": "Provider switch failed: OpenRouter authentication is not configured", "notifyType": "error"}}
Evidence: Pi RPC integration transcript with fixture providers

Source: Pi RPC integration transcript with fixture providers

{"id":"provider-switch-1","type":"response","command":"get_state","success":true,"data":{"model":{"id":"gpt-5.6-sol","name":"Fixture Starting","api":"openai-completions","provider":"openai-codex","baseUrl":"http://127.0.0.1:40099/quota","reasoning":false,"input":["text"],"cost":{"input":0,"output":0,"cacheRead":0,"cacheWrite":0},"contextWindow":4096,"maxTokens":256},"thinkingLevel":"off","isStreaming":false,"isCompacting":false,"steeringMode":"one-at-a-time","followUpMode":"one-at-a-time","sessionId":"01a0c530-5d27-7262-9ff2-271c425dc45b","autoCompactionEnabled":true,"messageCount":0,"pendingMessageCount":0}}
{"type":"extension_ui_request","id":"068530d7-bd9a-4218-b0de-9039d9996dcc","method":"notify","message":"Provider switch unavailable: pin supervision to an independent openai-codex model with /supervision-model first","notifyType":"error"}
{"id":"provider-switch-2","type":"response","command":"prompt","success":true}
{"id":"provider-switch-3","type":"response","command":"get_state","success":true,"data":{"model":{"id":"gpt-5.6-sol","name":"Fixture Starting","api":"openai-completions","provider":"openai-codex","baseUrl":"http://127.0.0.1:40099/quota","reasoning":false,"input":["text"],"cost":{"input":0,"output":0,"cacheRead":0,"cacheWrite":0},"contextWindow":4096,"maxTokens":256},"thinkingLevel":"off","isStreaming":false,"isCompacting":false,"steeringMode":"one-at-a-time","followUpMode":"one-at-a-time","sessionId":"01a0c530-5d27-7262-9ff2-271c425dc45b","autoCompactionEnabled":true,"messageCount":0,"pendingMessageCount":0}}
{"type":"extension_ui_request","id":"78ed44c1-6e4d-46db-bfe3-cf91c6fdc3cc","method":"notify","message":"Provider switch unavailable: pin supervision to an independent openai-codex model with /supervision-model first","notifyType":"error"}
{"id":"provider-switch-4","type":"response","command":"prompt","success":true}
{"id":"provider-switch-5","type":"response","command":"get_state","success":true,"data":{"model":{"id":"gpt-5.6-sol","name":"Fixture Starting","api":"openai-completions","provider":"openai-codex","baseUrl":"http://127.0.0.1:40099/quota","reasoning":false,"input":["text"],"cost":{"input":0,"output":0,"cacheRead":0,"cacheWrite":0},"contextWindow":4096,"maxTokens":256},"thinkingLevel":"off","isStreaming":false,"isCompacting":false,"steeringMode":"one-at-a-time","followUpMode":"one-at-a-time","sessionId":"01a0c530-5d27-7262-9ff2-271c425dc45b","autoCompactionEnabled":true,"messageCount":0,"pendingMessageCount":0}}
{"type":"extension_ui_request","id":"2fa3931f-64ef-481b-b87e-1b8862e09b29","method":"notify","message":"Provider switch unavailable: pin supervision to an independent openai-codex model with /supervision-model first","notifyType":"error"}
{"id":"provider-switch-6","type":"response","command":"prompt","success":true}
{"id":"provider-switch-7","type":"response","command":"get_state","success":true,"data":{"model":{"id":"gpt-5.6-sol","name":"Fixture Starting","api":"openai-completions","provider":"openai-codex","baseUrl":"http://127.0.0.1:40099/quota","reasoning":false,"input":["text"],"cost":{"input":0,"output":0,"cacheRead":0,"cacheWrite":0},"contextWindow":4096,"maxTokens":256},"thinkingLevel":"off","isStreaming":false,"isCompacting":false,"steeringMode":"one-at-a-time","followUpMode":"one-at-a-time","sessionId":"01a0c530-5d27-7262-9ff2-271c425dc45b","autoCompactionEnabled":true,"messageCount":0,"pendingMessageCount":0}}
{"type":"extension_ui_request","id":"c6ba6a11-87f5-4b31-9fd0-d7c348203e95","method":"notify","message":"Provider switch unavailable: pin supervision to an independent openai-codex model with /supervision-model first","notifyType":"error"}
{"id":"provider-switch-8","type":"response","command":"prompt","success":true}
{"id":"provider-switch-9","type":"response","command":"get_state","success":true,"data":{"model":{"id":"gpt-5.6-sol","name":"Fixture Starting","api":"openai-completions","provider":"openai-codex","baseUrl":"http://127.0.0.1:40099/quota","reasoning":false,"input":["text"],"cost":{"input":0,"output":0,"cacheRead":0,"cacheWrite":0},"contextWindow":4096,"maxTokens":256},"thinkingLevel":"off","isStreaming":false,"isCompacting":false,"steeringMode":"one-at-a-time","followUpMode":"one-at-a-time","sessionId":"01a0c530-5d27-7262-9ff2-271c425dc45b","autoCompactionEnabled":true,"messageCount":0,"pendingMessageCount":0}}
{"type":"extension_ui_request","id":"30c46b36-f87d-459b-9b54-36eaf1c1f51d","method":"notify","message":"Provider switch unavailable: pin supervision to an independent openai-codex model with /supervision-model first","notifyType":"error"}
{"id":"provider-switch-10","type":"response","command":"prompt","success":true}
{"id":"provider-switch-11","type":"response","command":"get_state","success":true,"data":{"model":{"id":"gpt-5.6-sol","name":"Fixture Starting","api":"openai-completions","provider":"openai-codex","baseUrl":"http://127.0.0.1:40099/quota","reasoning":false,"input":["text"],"cost":{"input":0,"output":0,"cacheRead":0,"cacheWrite":0},"contextWindow":4096,"maxTokens":256},"thinkingLevel":"off","isStreaming":false,"isCompacting":false,"steeringMode":"one-at-a-time","followUpMode":"one-at-a-time","sessionId":"01a0c530-5d27-7262-9ff2-271c425dc45b","autoCompactionEnabled":true,"messageCount":0,"pendingMessageCount":0}}
{"type":"extension_ui_request","id":"8156c75b-13c0-4800-90f6-a7756412c9a9","method":"notify","message":"Provider switch unavailable: pin supervision to an independent openai-codex model with /supervision-model first","notifyType":"error"}
{"id":"provider-switch-12","type":"response","command":"prompt","success":true}
{"id":"provider-switch-13","type":"response","command":"get_state","success":true,"data":{"model":{"id":"gpt-5.6-sol","name":"Fixture Starting","api":"openai-completions","provider":"openai-codex","baseUrl":"http://127.0.0.1:40099/quota","reasoning":false,"input":["text"],"cost":{"input":0,"output":0,"cacheRead":0,"cacheWrite":0},"contextWindow":4096,"maxTokens":256},"thinkingLevel":"off","isStreaming":false,"isCompacting":false,"steeringMode":"one-at-a-time","followUpMode":"one-at-a-time","sessionId":"01a0c530-5d27-7262-9ff2-271c425dc45b","autoCompactionEnabled":true,"messageCount":0,"pendingMessageCount":0}}
{"type":"extension_ui_request","id":"2fb24956-8cbc-47ad-8e2d-ef9489bba81f","method":"notify","message":"Provider switch unavailable: only the top-level Firstmate primary may switch","notifyType":"error"}
{"id":"provider-switch-14","type":"response","command":"prompt","success":true}
{"id":"provider-switch-15","type":"response","command":"get_state","success":true,"data":{"model":{"id":"gpt-5.6-sol","name":"Fixture Starting","api":"openai-completions","provider":"openai-codex","baseUrl":"http://127.0.0.1:40099/quota","reasoning":false,"input":["text"],"cost":{"input":0,"output":0,"cacheRead":0,"cacheWrite":0},"contextWindow":4096,"maxTokens":256},"thinkingLevel":"off","isStreaming":false,"isCompacting":false,"steeringMode":"one-at-a-time","followUpMode":"one-at-a-time","sessionId":"01a0c530-5d27-7262-9ff2-271c425dc45b","autoCompactionEnabled":true,"messageCount":0,"pendingMessageCount":0}}
{"type":"extension_ui_request","id":"4588caa2-d04c-4b36-a105-b72160d9df81","method":"notify","message":"Provider switch unavailable: only the top-level Firstmate primary may switch","notifyType":"error"}
{"id":"provider-switch-16","type":"response","command":"prompt","success":true}
{"id":"provider-switch-17","type":"response","command":"get_state","success":true,"data":{"model":{"id":"gpt-5.6-sol","name":"Fixture Starting","api":"openai-completions","provider":"openai-codex","baseUrl":"http://127.0.0.1:40099/quota","reasoning":false,"input":["text"],"cost":{"input":0,"output":0,"cacheRead":0,"cacheWrite":0},"contextWindow":4096,"maxTokens":256},"thinkingLevel":"off","isStreaming":false,"isCompacting":false,"steeringMode":"one-at-a-time","followUpMode":"one-at-a-time","sessionId":"01a0c530-5d27-7262-9ff2-271c425dc45b","autoCompactionEnabled":true,"messageCount":0,"pendingMessageCount":0}}
{"type":"extension_ui_request","id":"3a2fff06-523d-4920-9825-63c2bb71900f","method":"notify","message":"Provider switch unavailable: only the top-level Firstmate primary may switch","notifyType":"error"}
{"id":"provider-switch-18","type":"response","command":"prompt","success":true}
{"id":"provider-switch-19","type":"response","command":"get_state","success":true,"data":{"model":{"id":"gpt-5.6-sol","name":"Fixture Starting","api":"openai-completions","provider":"openai-codex","baseUrl":"http://127.0.0.1:40099/quota","reasoning":false,"input":["text"],"cost":{"input":0,"output":0,"cacheRead":0,"cacheWrite":0},"contextWindow":4096,"maxTokens":256},"thinkingLevel":"off","isStreaming":false,"isCompacting":false,"steeringMode":"one-at-a-time","followUpMode":"one-at-a-time","sessionId":"01a0c530-5d27-7262-9ff2-271c425dc45b","autoCompactionEnabled":true,"messageCount":0,"pendingMessageCount":0}}
{"type":"extension_ui_request","id":"a0b36dc3-2ce3-4ae5-b07e-56aed643def9","method":"notify","message":"Provider switch unavailable: only the top-level Firstmate primary may switch","notifyType":"error"}
{"id":"provider-switch-20","type":"response","command":"prompt","success":true}
{"id":"provider-switch-21","type":"response","command":"get_state","success":true,"data":{"model":{"id":"gpt-5.6-sol","name":"Fixture Starting","api":"openai-completions","provider":"openai-codex","baseUrl":"http://127.0.0.1:40099/quota","reasoning":false,"input":["text"],"cost":{"input":0,"output":0,"cacheRead":0,"cacheWrite":0},"contextWindow":4096,"maxTokens":256},"thinkingLevel":"off","isStreaming":false,"isCompacting":false,"steeringMode":"one-at-a-time","followUpMode":"one-at-a-time","sessionId":"01a0c530-5d27-7262-9ff2-271c425dc45b","autoCompactionEnabled":true,"messageCount":0,"pendingMessageCount":0}}
{"type":"extension_ui_request","id":"6323cb0c-b82a-4bc0-9c40-3b251d451b8f","method":"notify","message":"Provider switch unavailable: only the top-level Firstmate primary may switch","notifyType":"error"}
{"id":"provider-switch-22","type":"response","command":"prompt","success":true}
{"id":"provider-switch-23","type":"response","command":"get_state","success":true,"data":{"model":{"id":"gpt-5.6-sol","name":"Fixture Starting","api":"openai-completions","provider":"openai-codex","baseUrl":"http://127.0.0.1:40099/quota","reasoning":false,"input":["text"],"cost":{"input":0,"output":0,"cacheRead":0,"cacheWrite":0},"contextWindow":4096,"maxTokens":256},"thinkingLevel":"off","isStreaming":false,"isCompacting":false,"steeringMode":"one-at-a-time","followUpMode":"one-at-a-time","sessionId":"01a0c530-5d27-7262-9ff2-271c425dc45b","autoCompactionEnabled":true,"messageCount":0,"pendingMessageCount":0}}
{"type":"extension_ui_request","id":"ae5c7a77-9b52-4b6f-8342-9b0c9cef2c6f","method":"notify","message":"probe settled: idle","notifyType":"info"}
{"id":"provider-switch-24","type":"response","command":"prompt","success":true}
{"type":"extension_ui_request","id":"dcf31860-cc84-4341-93ee-9e35da7f912d","method":"notify","message":"Primary session switched to openrouter/openai/gpt-5.6-sol","notifyType":"info"}
{"id":"provider-switch-25","type":"response","command":"prompt","success":true}
{"id":"provider-switch-26","type":"response","command":"get_state","success":true,"data":{"model":{"id":"openai/gpt-5.6-sol","name":"OpenRouter GPT-5.6 Sol","api":"openai-completions","provider":"openrouter","baseUrl":"http://127.0.0.1:40099/openrouter","reasoning":true,"input":["text"],"cost":{"input":0,"output":0,"cacheRead":0,"cacheWrite":0},"contextWindow":1050000,"maxTokens":128000,"compat":{"thinkingFormat":"openrouter"}},"thinkingLevel":"off","isStreaming":false,"isCompacting":false,"steeringMode":"one-at-a-time","followUpMode":"one-at-a-time","sessionId":"01a0c530-5d27-7262-9ff2-271c425dc45b","autoCompactionEnabled":true,"messageCount":0,"pendingMessageCount":0}}
{"type":"extension_ui_request","id":"d8a81959-717a-49a9-b746-f6149149a409","method":"notify","message":"probe settled: supervision branch provider failed after construction: 401: {\"message\":\"local routing probe complete\"}","notifyType":"error"}
{"id":"provider-switch-27","type":"response","command":"prompt","success":true}
{"id":"provider-switch-28","type":"response","command":"get_state","success":true,"data":{"model":{"id":"openai/gpt-5.6-sol","name":"OpenRouter GPT-5.6 Sol","api":"openai-completions","provider":"openrouter","baseUrl":"http://127.0.0.1:40099/openrouter","reasoning":true,"input":["text"],"cost":{"input":0,"output":0,"cacheRead":0,"cacheWrite":0},"contextWindow":1050000,"maxTokens":128000,"compat":{"thinkingFormat":"openrouter"}},"thinkingLevel":"off","isStreaming":false,"isCompacting":false,"steeringMode":"one-at-a-time","followUpMode":"one-at-a-time","sessionId":"01a0c530-5d27-7262-9ff2-271c425dc45b","autoCompactionEnabled":true,"messageCount":0,"pendingMessageCount":0}}
{"id":"fresh-worker","type":"response","command":"get_state","success":true,"data":{"model":{"id":"gpt-5.6-sol","name":"Fixture Starting","api":"openai-completions","provider":"openai-codex","baseUrl":"http://127.0.0.1:40099/quota","reasoning":false,"input":["text"],"cost":{"input":0,"output":0,"cacheRead":0,"cacheWrite":0},"contextWindow":4096,"maxTokens":256},"thinkingLevel":"off","isStreaming":false,"isCompacting":false,"steeringMode":"one-at-a-time","followUpMode":"one-at-a-time","sessionId":"01a0c530-68bc-72d5-be49-1490fe187f2b","autoCompactionEnabled":true,"messageCount":0,"pendingMessageCount":0}}
{"type":"extension_ui_request","id":"ac7dd8ab-0382-49ac-94a9-a22194fc95c4","method":"notify","message":"Usage: /fm-openrouter-sol (no arguments)","notifyType":"error"}
{"id":"provider-switch-29","type":"response","command":"prompt","success":true}
{"id":"provider-switch-30","type":"response","command":"get_state","success":true,"data":{"model":{"id":"openai/gpt-5.6-sol","name":"OpenRouter GPT-5.6 Sol","api":"openai-completions","provider":"openrouter","baseUrl":"http://127.0.0.1:40099/openrouter","reasoning":true,"input":["text"],"cost":{"input":0,"output":0,"cacheRead":0,"cacheWrite":0},"contextWindow":1050000,"maxTokens":128000,"compat":{"thinkingFormat":"openrouter"}},"thinkingLevel":"off","isStreaming":false,"isCompacting":false,"steeringMode":"one-at-a-time","followUpMode":"one-at-a-time","sessionId":"01a0c530-5d27-7262-9ff2-271c425dc45b","autoCompactionEnabled":true,"messageCount":0,"pendingMessageCount":0}}
{"type":"extension_ui_request","id":"9513c017-ce52-4bb6-b8de-83527f88c620","method":"notify","message":"Provider switch unavailable: this session does not own the Firstmate primary lock","notifyType":"error"}
{"id":"provider-switch-31","type":"response","command":"prompt","success":true}
{"id":"provider-switch-32","type":"response","command":"get_state","success":true,"data":{"model":{"id":"openai/gpt-5.6-sol","name":"OpenRouter GPT-5.6 Sol","api":"openai-completions","provider":"openrouter","baseUrl":"http://127.0.0.1:40099/openrouter","reasoning":true,"input":["text"],"cost":{"input":0,"output":0,"cacheRead":0,"cacheWrite":0},"contextWindow":1050000,"maxTokens":128000,"compat":{"thinkingFormat":"openrouter"}},"thinkingLevel":"off","isStreaming":false,"isCompacting":false,"steeringMode":"one-at-a-time","followUpMode":"one-at-a-time","sessionId":"01a0c530-5d27-7262-9ff2-271c425dc45b","autoCompactionEnabled":true,"messageCount":0,"pendingMessageCount":0}}
Evidence: Live inbox concurrency and acknowledgement responses

Source: Live inbox concurrency and acknowledgement responses

{"concurrent_submissions": [{"schema": "fm-inbox-note.v1", "outcome": "created", "id": "1790014739-RIuKp1", "request_id": "live-concurrent", "saved": true, "announced": true, "acknowledged": false, "path": "~/.no-mistakes/worktrees/34664d32b09d/01M32JAB16PW614MA93BBP214M/.test-phase-tmp/inbox-live/state/inbox/1790014739-RIuKp1.note"}, {"schema": "fm-inbox-note.v1", "outcome": "replay", "id": "1790014739-RIuKp1", "request_id": "live-concurrent", "saved": true, "announced": true, "acknowledged": false, "path": "~/.no-mistakes/worktrees/34664d32b09d/01M32JAB16PW614MA93BBP214M/.test-phase-tmp/inbox-live/state/inbox/1790014739-RIuKp1.note"}, {"schema": "fm-inbox-note.v1", "outcome": "replay", "id": "1790014739-RIuKp1", "request_id": "live-concurrent", "saved": true, "announced": true, "acknowledged": false, "path": "~/.no-mistakes/worktrees/34664d32b09d/01M32JAB16PW614MA93BBP214M/.test-phase-tmp/inbox-live/state/inbox/1790014739-RIuKp1.note"}, {"schema": "fm-inbox-note.v1", "outcome": "replay", "id": "1790014739-RIuKp1", "request_id": "live-concurrent", "saved": true, "announced": true, "acknowledged": false, "path": "~/.no-mistakes/worktrees/34664d32b09d/01M32JAB16PW614MA93BBP214M/.test-phase-tmp/inbox-live/state/inbox/1790014739-RIuKp1.note"}, {"schema": "fm-inbox-note.v1", "outcome": "replay", "id": "1790014739-RIuKp1", "request_id": "live-concurrent", "saved": true, "announced": true, "acknowledged": false, "path": "~/.no-mistakes/worktrees/34664d32b09d/01M32JAB16PW614MA93BBP214M/.test-phase-tmp/inbox-live/state/inbox/1790014739-RIuKp1.note"}, {"schema": "fm-inbox-note.v1", "outcome": "replay", "id": "1790014739-RIuKp1", "request_id": "live-concurrent", "saved": true, "announced": true, "acknowledged": false, "path": "~/.no-mistakes/worktrees/34664d32b09d/01M32JAB16PW614MA93BBP214M/.test-phase-tmp/inbox-live/state/inbox/1790014739-RIuKp1.note"}]}
{"ack": "acked 1790014739-RIuKp1\n"}
{"command": ["note", "--request-id", "live-concurrent", "--json", "retry"], "response": {"schema": "fm-inbox-note.v1", "outcome": "replay", "id": "1790014739-RIuKp1", "request_id": "live-concurrent", "saved": true, "announced": true, "acknowledged": true, "path": "~/.no-mistakes/worktrees/34664d32b09d/01M32JAB16PW614MA93BBP214M/.test-phase-tmp/inbox-live/state/inbox/handled/1790014739-RIuKp1.note"}}
{"command": ["announce", "--json", "1790014739-RIuKp1"], "response": {"schema": "fm-inbox-note.v1", "outcome": "replay", "id": "1790014739-RIuKp1", "request_id": null, "saved": true, "announced": true, "acknowledged": true, "path": "~/.no-mistakes/worktrees/34664d32b09d/01M32JAB16PW614MA93BBP214M/.test-phase-tmp/inbox-live/state/inbox/handled/1790014739-RIuKp1.note"}}
{"reply": {"schema": "fm-inbox-reply.v1", "outcome": "created", "id": "1790014739-RIuKp1", "path": "~/.no-mistakes/worktrees/34664d32b09d/01M32JAB16PW614MA93BBP214M/.test-phase-tmp/inbox-live/state/inbox/.replies/1790014739-RIuKp1"}}
{"receipts": {"schema": "fm-inbox-receipts.v1", "home": ".test-phase-tmp/inbox-live", "generated": "2026-09-21T18:19:01Z", "pending": [], "handled": [{"id": "1790014739-RIuKp1", "at": "2026-09-21T18:18:59Z", "source": "text", "request_id": "live-concurrent", "body": "live capture", "acknowledged": true, "announced": true, "reply": {"id": "1790014739-RIuKp1", "at": "2026-09-21T18:19:01Z", "body": "captured once and handled", "cursor": "000000000001"}}], "replies": [{"id": "1790014739-RIuKp1", "at": "2026-09-21T18:19:01Z", "body": "captured once and handled", "cursor": "000000000001"}], "reply_cursor": "000000000001", "omitted": []}}
- Outcome: ⚠️ 1 warning across 1 run (3m5s)

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

🔧 **Rebase** - 13 issues found → auto-fixed ✅
  • ⚠️ .agents/skills/afk/SKILL.md - merge conflict rebasing onto origin/main
  • ⚠️ .pi/extensions/fm-branch-supervision.ts - merge conflict rebasing onto origin/main
  • ⚠️ bin/fm-branch-prompt.sh - merge conflict rebasing onto origin/main
  • ⚠️ bin/fm-lease-lib.sh - merge conflict rebasing onto origin/main
  • ⚠️ bin/fm-pr-merge.sh - merge conflict rebasing onto origin/main
  • ⚠️ bin/fm-spawn.sh - merge conflict rebasing onto origin/main
  • ⚠️ docs/architecture.md - merge conflict rebasing onto origin/main
  • ⚠️ docs/pi-supervision-branch.md - merge conflict rebasing onto origin/main
  • ⚠️ docs/verification/runtime-backends.md - merge conflict rebasing onto origin/main
  • ⚠️ tests/fm-branch-supervision.test.sh - merge conflict rebasing onto origin/main
  • ⚠️ tests/fm-pi-branch-extension.test.sh - merge conflict rebasing onto origin/main
  • ⚠️ tests/fm-pr-merge.test.sh - merge conflict rebasing onto origin/main
  • ⚠️ tests/fm-send-resolve-key.test.sh - merge conflict rebasing onto origin/main

🔧 Fix applied.
✅ Re-checked - no issues remain.

🔧 **Review** - 3 issues found → auto-fixed ✅
  • 🚨 bin/fm-inbox.sh:457 - Concurrent submissions with the same request ID can resurrect an acknowledged note. After caller A reserves the ID, caller B can publish it through publish_from_reservation(), announce it, and have it acknowledged before A reaches this unconditional rename. A then recreates the pending note despite its handled copy. Serialize reservation, publication, and acknowledgement at a shared boundary so retries cannot republish completed work.
  • 🚨 bin/fm-inbox.sh:784 - Receipts can permanently skip a reply during concurrent writes. If enrich(A) observes no reply, writers then publish A at sequence 1 and B at sequence 2, and enrich(B) observes sequence 2, the response advances reply_cursor to 2 without returning sequence 1. Subsequent --after requests exclude the missed reply. Read the reply snapshot under the existing reply-sequence lock, or otherwise prevent the cursor advancing beyond unseen replies.
  • ⚠️ bin/fm-inbox.sh:373 - Replaying a normally announced, subsequently acknowledged note reports acknowledged=false. announce_note() returns success immediately when its announcement marker exists, so this function sets only announced=1; finding the handled file updates the path without updating acknowledgement. The already-announced branch of cmd_announce() has the same defect. Derive acknowledgement independently from announcement state in both responses.

🔧 Fix applied.
✅ Re-checked - no issues remain.

⚠️ **Test** - 1 warning
  • ⚠️ live validation verdict: inconclusive (2 of 7 scenarios were driven live against the product); untested: Main switches to OpenRouter while supervision and fresh workers retain quota-provider models, Malformed pins, secondmate markers, extra arguments, and lost primary ownership cannot change model selection, Worker dispatch respects account-specific quota and refuses exhausted or ambiguous choices, Concurrent reply publication cannot advance receipts past an unseen reply, Main completes an authenticated OpenRouter Sol turn after switching
  • Live validation: ⚠️ inconclusive - 2 of 7 scenarios driven live against the product
Scenario Result Live Evidence
Main switches to OpenRouter while supervision and fresh workers retain quota-provider models ⏸️ untested no The prior payload recorded only integration checks using fixture credentials and loopback provider transport, not a live result. Authorized test credentials and live provider execution are needed to e…
Worker sessions, missing supervision pins, and missing credentials refuse the switch ✅ pass live live-guards.jsonl
Malformed pins, secondmate markers, extra arguments, and lost primary ownership cannot change model selection ⏸️ untested no The prior payload marked this non-live and recorded fixture model configuration; it did not establish a live result. This scenario needs execution against a live product configuration.
Worker dispatch respects account-specific quota and refuses exhausted or ambiguous choices ⏸️ untested no The prior payload recorded quota and resolver fixture checks only, not a live result. Live account quota and dispatch execution are needed to establish this scenario.
Concurrent inbox retries preserve one note and report acknowledgement after handling ✅ pass live inbox-live.jsonl; tests/fm-inbox.test.sh
Concurrent reply publication cannot advance receipts past an unseen reply ⏸️ untested no The prior payload recorded a controlled concurrency regression only, not a live result. Concurrent reply publication and receipt handling must be exercised against the live product.
Main completes an authenticated OpenRouter Sol turn after switching ⏸️ untested no No test OpenRouter credential was configured in the isolated environment. Provide an authorized test credential and rerun this scenario.
  • bin/fm-test-run.sh tests/fm-pi-provider-switch.test.sh with isolated temporary storage and RPC capture
  • bin/fm-test-run.sh --jobs 1 tests/fm-inbox.test.sh tests/fm-quota-choose.test.sh tests/fm-dispatch-resolve.test.sh
  • python3 .test-phase-tmp/live.py: three isolated real Pi refusal scenarios
  • python3 .test-phase-tmp/inbox-live.py: concurrent capture, acknowledgement, replay, announcement, reply, and receipts
  • git remote -v: origin targets dscott98/firstmate
  • git status --short: clean after temporary test cleanup
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

kunchenguid and others added 14 commits September 21, 2026 11:07
)

* feat(procevent): route worker-owned Lavish rounds

* no-mistakes(review): drop duplicate artifact field from task-owned registration

* no-mistakes(review): post worker reply once, fix ring label, keep re-arm atomic

* no-mistakes(review): keep worker board owned until terminal round acknowledged

* no-mistakes(review): refuse every retirement of an open worker-owned round

* no-mistakes(review): use real lavish reply flag, isolate reply generations

* no-mistakes(review): drop .posted marker for best-effort reply posting

* no-mistakes(review): consume staged reply after listener setup, refuse orphaned captures

* no-mistakes(review): require a reachable owner, redeliver open rounds, roll back failed re-arms

* no-mistakes(review): re-arm only to acknowledge an open round

* no-mistakes(review): conclude only a still-open terminal round

* no-mistakes(review): record the acknowledgement before retiring the board

* no-mistakes(review): retain the registration across a conclude, qualify terminal docs

* no-mistakes(document): Document worker-owned Lavish round lifecycle
…unchenguid#5107)

* fix(bin): reserve contribution observation budget

* no-mistakes(review): Strengthen slow-read regression test to exceed the poll budget
…ness JSON (kunchenguid#5103)

* feat(bin): add idempotent inbox orders, receipts, replies, and readiness

Let a caller supply a request id when publishing a captain inbox note so a
retry returns the original note instead of creating a second one, including
across the crash window between save and wake announcement. Separate saved
from announced so a failed wake is repairable without enqueueing again.
Add bounded receipts JSON with omission disclosure, a durable primary reply
against a note id, and a read-only readiness projection that can say
unknown instead of inferring liveness from a lock file.

* no-mistakes(review): fix(bin): honest inbox announce, reply cursor, and readiness verdict

* fix(bin): resolve ready from lock-holder ancestry; drop lock status --json

Remove the extra JSON surface from fm-lock.sh so its human status still
always exits zero. Have the readiness projection classify the inspected
home from the lock-holder pid via fm-harness.sh ancestry, with an explicit
FM_SUPERVISION_MODEL still winning and an unknown model when there is no
holder. Prove the yes path when that ancestry names a known harness.

* no-mistakes(review): Harden inbox announce, receipts reads, and reply sequence cursor

* no-mistakes(document): Note read-only lock inspection in scripts inventory

* no-mistakes(lint): Pass missing id argument to malformed-reply test printf

---------

Co-authored-by: cliflacata-svg <304148223+cliflacata-svg@users.noreply.github.com>
…ending text (kunchenguid#5118)

* fix(composer): stop a harness footer row from reading as a composer holding text

A harness draws its own furniture below the composer - a user statusLine, a
permission-mode hint - and the cursorless "bottom-most shape wins" rule looks
exactly there. `→` (U+2192) is Cursor's prompt glyph but ordinary text
everywhere else, so a statusLine opening with `→` was selected as a bare
composer, swallowed the hint row beneath it as wrapped input, and answered
`pending` on a visibly empty pane. `fm_task_inbox_ring` defers on exactly that
verdict, and `bin/fm-watch.sh`'s re-ring calls the same function, so the first
doorbell and every retry were skipped and the worker never saw the steer.

Measured live on 2026-09-20: three of five Claude Code 2.1.236 worker panes on
Herdr 0.8.0 had genuinely empty composers and every one of them was refused.

A separator pair that closed over a bare agent-glyph row is a proven composer
container, so the contiguous non-blank rows below its closing rule are that
composer's footer and are no longer composer candidates. The demotion is bounded
by all three of its own preconditions: a blank row ends the zone, a pair that
closed over no glyph row demotes nothing, and a shape with no separator pair at
all (Cursor's half-block rules) is untouched. Real unsubmitted text in that same
composer, including a stray SGR mouse report left by a click in the pane, still
reads `pending`.

Pinned by two portable regressions and by a new cursorless arm on the live
composer-matrix guard, which re-reads each harness's already-proven-idle pane
the way every non-tmux backend reads it and fails naming the harness and
version when that read is `pending`.

* no-mistakes(review): make composer footer-zone demotion shape-independent

* no-mistakes(review): make footer-zone demotion refuse-only and drop rescan

* no-mistakes(lint): quote probe-absent sentinel to clear ShellCheck SC2100

---------

Co-authored-by: Koen Muller <koen@catapult.nl>
…5115)

Co-authored-by: guanchengh-lgtm <271917158+guanchengh-lgtm@users.noreply.github.com>
… an unreadable runs table (kunchenguid#5114)

* fix(bin): stop misreading a no-run branch as an unreadable runs table

Defect: when `no-mistakes axi status`'s overview is truncated (a task's
own branch has zero rows among the shown ones), fm_nm_select_run's
Python fallback derived the repo identity for its direct SQLite query
from a `repo: <path>` line it expected in the overview text. The real
CLI never emits that line, truncated or not (see the genuine capture at
tests/captures/no-mistakes-v1.70.1/overview.toon, which has only
`count:`/`runs[...]:`), so the lookup always failed and reported
"unreadable runs table" for a task that simply has no run on its
branch. On a fleet with many concurrent runs, every idle-branch task
hits the truncated-overview path routinely, so this fired every few
minutes and drowned genuine unreadable/blocked verdicts in noise.

Fix: derive the repo identity from the task worktree path instead,
which is exactly the value `no-mistakes` records as a repo's
`working_path` (confirmed against the existing capped-overview test
fixtures, which already register repos by worktree path). A worktree
path that is not absolute cannot be matched and still reads as
unreadable rather than being guessed at. Also raise the reader's
SQLite busy timeout from 1s to 30s so ordinary lock contention on a
busy fleet cannot masquerade as an unreadable database.

Safety: every other verdict byte-for-byte unchanged - the repo lookup
still requires exactly one matching row (a genuinely corrupt or
mismatched repos table still reports unreadable, per the existing
`repo` failure-mode test), the branch query and row validation are
untouched, and a zero-row result for the branch still flows through
the same recursive re-parse that already turns an empty `runs[0]{...}`
table into `absent`. Added a regression test
(test_capped_overview_without_repo_line_and_no_runs_reports_absent)
that reproduces the real overview shape - capped, zero rows for the
task's branch, no `repo: ` line - and asserts the crew state falls
through to the pane/busy verdict instead of reporting unknown or
"unreadable". Full fm-crew-state.test.sh suite passes unchanged
otherwise.

* fix: recovered same-branch inventory awk misreads empty result as unreadable

fm_nm_select_run's deep SQLite reader rebuilds a `count:`/`runs[...]:`
overview and re-runs it through the same awk selection pass. When that
rebuilt inventory has zero rows for the branch, the row-matching loop never
executes, so its counters (`seen`) stay at awk's uninitialized empty string
while `expected` and `shown` are plain strings parsed from the header text.
Comparing an uninitialized value against a non-numeric string uses string
comparison, so "" != "0" is true, and the END block takes the "unreadable
runs table" branch instead of falling through to the correct "absent"
verdict for a branch with genuinely zero runs.

Coerce the affected END comparisons with `+0` so they are always numeric,
matching seen/expected/shown/total regardless of whether awk classified
them as strings or numeric strings. A truncated or genuinely malformed
inventory still differs numerically and still reports unreadable.

* no-mistakes(review): bound capped-overview inventory reader and canonicalize worktree lookup

* no-mistakes(review): match recorded repo path first, tolerate duplicate spellings

* no-mistakes(review): revert repo lookup to exact working_path match

* no-mistakes(document): note state-db inventory read under crew-state nm timeout
…ort (kunchenguid#5141)

* fix(bin): require a non-draft pull request before a PR-based done report

A PR-based ship could report done, and merge monitoring could be armed, while the pull request was still a draft. A draft cannot be merged, so the poll waited for an event that could not occur and nobody was asked to merge.

The PR-based definitions of done now require reading the pull request back from the forge and confirming it is not a draft, and a lane that deliberately holds a draft declares a wait instead of done.
bin/fm-pr-check.sh refuses to arm merge monitoring on a draft, naming the draft state, and treats an unreadable draft state as before.
The draft reading now lives in bin/fm-pr-lib.sh and bin/fm-pr-merge.sh uses it, with its refusal to merge a draft unchanged.

Closes kunchenguid#4757

* fix(review): Skip arm-time draft refusal when fm-pr-merge records metadata
* fix(bin): accept quota-axi schema 6 snapshots keyed by provider + accountKey

quota-axi 0.1.47 emits schemaVersion 6 once a provider expands to more
than one account: every provider row carries an accountKey and one
provider id may appear on several rows. fm_quota_json_valid accepted
only schema 5 with unique provider ids, so fm-dispatch-resolve.sh,
fm-quota-choose.sh, and fm-procevent-quota.sh all rejected the live
snapshot and quota-informed dispatch was dead against the current tool.

- bin/fm-quota-axi-lib.sh: the validator accepts schema 6 with
  accountKey required on every row and uniqueness on
  provider + accountKey; schema 5 keeps its exact rules. FM_QUOTA_ROW_JQ
  is the one join every consumer uses: schema 5 binds by provider alone,
  schema 6 binds to the row keyed by the candidate's Pi lane, else the
  provider's default row, else no row (unmeasured, never blocked, never
  by position or summed across accounts).
- bin/fm-quota-choose.sh: accepts schema 6 JSON and the TOON accountKey
  column, and joins through the shared function.
- bin/fm-dispatch-resolve.sh and bin/fm-procevent-quota.sh: join through
  the shared function; an expanded provider with no row for the
  candidate's account is reported as such.
- tests: schema 6 fixtures shaped like the real snapshot, each paired
  with a schema 5 case on the same path; every new case fails on the
  previous scripts and passes now.
- docs: the two sentences naming the row join describe the schema 6 key.

* no-mistakes(review): Fix native Codex quota and expanded provider watches

* no-mistakes(review): Align native Codex account matching across dispatch paths

* no-mistakes(document): Align quota documentation with account-aware snapshots

* no-mistakes(document): Align quota dispatch documentation with account matching

* fix(bin): keep CI lint and the quota watch test portable

- bin/fm-quota-axi-lib.sh: FM_QUOTA_ROW_JQ is read only by the scripts
  that source this library, so full-mode ShellCheck reported SC2034 on
  the assignment; mark it alongside the existing SC2016 disable.
- tests/fm-procevent-quota.test.sh: the schema 6 provider-watch
  assertions used rg, which CI runners do not install, so the case
  failed with 'rg: command not found' rather than on behavior; use grep
  like the rest of the file.

* no-mistakes(document): Documented schema-version account-row compatibility
@dscott98
dscott98 merged commit d659acd into main Sep 21, 2026
19 checks passed
dscott98 added a commit that referenced this pull request Sep 23, 2026
Integrate the fork's own main line (fork PRs #1-#4 plus the fork-only
upstream sync merge) so the delivery branch descends from the actual
fork main and the pull-request merge ref becomes buildable.

# Conflicts:
#	bin/fm-spawn.sh
#	bin/fm-test-run.sh
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants