M2+M3(M-MISSION-LOOP-UNIFIED-TELEMETRY): stage accounting + node-generic cloud dual-write - #697
Merged
sunholo-voight-kampff merged 4 commits intoAug 13, 2026
Conversation
…come, and the money adds up mission:v1/iter-190 spanned Anthropic, OpenAI and OpenRouter and read back as four `pending` stages, two of them recording $0.0570/$0.0507 at ZERO tokens, under a chain total of $0.0000 against $0.1077 actually spent. Two defects with DIFFERENT owners — fixing "mission accounting" as one thing patches one and misses the other. WRITER-SIDE (status). PostIteration created each stage and never transitioned it, so every stage kept CreateStage's `pending` default forever. IterationStage now carries Status from the EXISTING ChainStageStatus vocabulary (no new enum), applied via UpdateStageStatus. An unknown value is rejected in Validate rather than coerced: coercion would report an outcome the caller never claimed. CALLER-SIDE (tokens). The writer already forwarded tokens_in/tokens_out to UpdateStageMetrics — the zeros came from the poster. The mission-control skill now supplies per-stage token counts, and the CLI echoes the totals it recorded plus a stderr notice naming how many stages posted no status, so a payload that forgot them is visible at the call site instead of weeks later in a cost rollup. Stage costs/tokens now aggregate into the chain total, and a chain whose stages ALL reported terminal closes as completed — or failed if any stage failed. That derivation is deliberately gated on every stage having reported: a legacy or mid-flight post leaves the chain `active`, which is today's behaviour. The criterion that blocks the shortcut: setting every stage to `completed` would satisfy "nothing stays pending" while HIDING real failures. A failed stage reads back `failed`, pinned by a test built from the real iter-190 shape (4 stages, 3 providers, two carrying cost with zero tokens), and the skill instruction says so in as many words. Version skew is a contract, not a nicety — the skill and the CLI ship independently, so a payload that omits Status still works and behaves exactly as before (its money still aggregates; only the outcome is unreported). Verified end-to-end through the built binary against a throwaway HOME, not just in tests: the iter-190 payload reads back 4 stages completed/failed, $0.1077, 37414 tokens, chain `failed` — against all-pending/$0.0000/0-tokens before. 7 new tests. make test PASS, lint clean, check-boundaries / file-sizes / check-skills / check-changelog green. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…3 dual-write switch Re-applies the M2 caller-side instruction lost to a concurrent write of this file (the live mission-control iteration rewrites its own SKILL.md), and adds M3. The skill is the poster: iter-190's $0.0570/$0.0507-at-zero-tokens and its four `pending` stages came from this payload, not from the writer. So the template now carries `status` on every stage and tokens on every metered one, with the do-NOT-blanket-complete rule stated where the payload is built — a stage that failed must be posted `failed`, or the record hides the failures it exists to surface. Omitting `status` stays valid so an older loop never breaks. Also documents AILANG_CHAINS_CLOUD: dual-write to a remote observatory under the same ids, own bounded spool, never blocks, and unset = unchanged. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…mote observatory, node-generic `chains post-iteration` wrote to ONE hardcoded local SQLite path. A node that names a remote target — AILANG_CHAINS_CLOUD=gcp or --cloud gcp — now writes each iteration to its local store AND to that one. Unset, the behaviour is what it was, which the tests assert rather than assume (openPostTargets returns exactly one target). SELECTION reuses the AILANG_STORAGE resolver: NewBackends now delegates to NewBackendsForMode(ctx, mode), and the command passes an explicit mode. One switch with the mode as a parameter, not a second local/gcp/hybrid selector — and this node's own AILANG_STORAGE is untouched, so its coordinator and messaging stores stay put. A remote target that resolves to this node's OWN store is rejected with an explanation rather than quietly writing to itself. IDENTITY is the part that makes the remote copy useful instead of an island. ChainCreateRequest/StageCreateRequest take an optional ID, and PostIteration backfills the post with what it wrote, so the second target reproduces the SAME chain and stage ids and a span carrying them joins either copy. A PINNED id is never regenerated by CreateStage's UNIQUE-constraint retry: that retry exists for stage_number contention, and silently swapping a pinned id would look like success while breaking the identity it was pinned for. PostIteration now takes the Backend INTERFACE — one writer, two targets; a second implementation would be a second set of bugs. That required UpdateStageEvalAssessment on the interface, implemented across all five backends including Firestore (which is what both deployed observatories actually run). A remotely-posted stage without its model cannot be rate-classified cloud-side, so this is load-bearing, not tidiness. NEVER-BLOCK, the ratified requirement, EXTENDS the existing bounded+loud spool rather than inventing a policy — but each target gets its OWN. Sharing one would let a long cloud outage evict local posts that were only waiting on a locked database; separate spools keep both caps (100 entries / 1 MiB) intact per target. Verified end-to-end through the built binary, not only in tests: the same chain id lands in two distinct stores; a genuinely broken cloud config (gcp with no project) produces a loud stderr notice, a buffered post, rc=0 and a local write that still succeeded; and the buffered posts flush on the next run keeping their pinned ids. 8 new tests (6 cmd + 2 observatory). make test PASS, lint clean, check-boundaries / file-sizes / check-skills / check-changelog / check-golden-drift / verify-no-shim / verify-examples / test-nightly-classifier / test-regression-guards all green. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…n is what remains M1/M2/M3 all landed; sprint JSON reads implementation_complete_pending_evaluation. Records what was verified end-to-end (not only in tests), and four follow-ups in priority order — the first being that M1's session->chain linkage reaches the EVAL path only: internal/eval_harness/ai_agent.go:250 is the sole production site that sets OpenRouter correlation, and the mission loop's quorum calls run outside it, so no session.id arrives for a mission span. M3's ChainID pinning is the mechanism that closes it. Adds a fourth trap: staging an explicit path is NOT sufficient on this shared tree. The live mission-control iteration rewrites its own SKILL.md while running, and 0881761 swept its in-flight edit while losing ours. Verify the file, then verify git show HEAD:<path>. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
sunholo-voight-kampff
deleted the
sprint/iter193-mission-telemetry-m2m3
branch
August 13, 2026 16:19
sunholo-voight-kampff
added a commit
that referenced
this pull request
Aug 13, 2026
…ied and landed (#697), #698 filed Iteration 193 ran the full inner loop, committed 4 commits into the main checkout's dev, exited rc=0, and left zero charter rows and zero log rows. Found by Gate 2's died-mid-flight sweep and landed as inherited work rather than redone. - charter: ITERATION 194 stamp; ITERATION 190 rotated to the archive (line-count invariant asserted, and the archive end asserted to have gained it) - charter: a LANDED queue row for M-MISSION-LOOP-UNIFIED-TELEMETRY, which never had one - log: entry 195, crediting the orphaned iteration 193 - dashboard: overwritten Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



M-MISSION-LOOP-UNIFIED-TELEMETRY — M2 + M3
Lands the implementation completed by mission iteration 193 (2026-08-13 13:58–15:18), which
finished the work, committed it to the main checkout's
dev, and died before pushing or recording.This PR is iteration 194's verify-and-land of that orphaned work — not a redo.
088176104e4c45eb4f769d920a02b5cfa976M2 — two defects with different owners:
PostIterationnever calledUpdateStageStatus(writer), and the poster supplied zero tokens (caller).
IterationStagegainedStatus; stagecost/tokens now aggregate into the chain total. Measured on the
iter-190shape through the builtbinary: 4 stages
pending/ $0.0000 / 0 tokens → completed+failed / $0.1077 / 37,414 tokens.M3 —
chains post-iterationdual-writes to the local store and, when a node names one viaAILANG_CHAINS_CLOUD/--cloud, a remote one, under the same chain and stage ids so spansjoin either copy. Each target keeps its own bounded spool; an unreachable remote is a loud stderr
notice plus a buffered retry, never a blocked iteration.
Design freeze items 1–3 were ratified by Mark on 2026-08-13 and are recorded verbatim in the sprint
JSON's
design_freezeblock.Co-Authored-By: codex gpt-5.6-sol
🤖 Generated with Claude Code