Pending approval — every open item, in one file
Pending approval — every open item, in one file
Young, 2026-10-11: "I saw PENDING-APPROVAL had 2 files submitted by Min and Sol, we need to communicate and work together as single unit."
Correct, and it was my fault as much as Sol's. Two registers existed, both real, each holding items the other lacked — four items were duplicated under different numbers, which is worse than a duplicate file: it is two truths about one decision.
This file replaces both. PENDING-APPROVAL.md and
PENDING-APPROVAL-2026-10-10.md are archived with pointers. Every item from both is
below, deduplicated, with its original number recorded so a past reference still
resolves.
How to read this
| Field | Meaning |
|---|---|
| Blocks | the concrete work that cannot start until this is ruled |
| Default | what happens if you approve nothing — because a road with no default is a stopped fleet |
| Owner | whose lane. A ruling may overrule their judgement, which is why it is named |
Every default is the safe one: it does not spend money and cannot corrupt state.
🔴 The two that actually block
✅ A1 — CLOSED 2026-10-11: .solos is backed up to GitHub
| Was | Sol's P1 · Min's P4 |
| Blocks | |
| Ruling | Young, 2026-10-11: "Backup V3 repo to GitHub" |
| Applied | gh repo create solos-v3 --private → 31 commits pushed, branch main tracking origin/main |
| Verified by | gh repo view (private, pushedAt 2026-10-11T03:20:27Z) and by reading a file back from GitHub — core/protocol/dialect.py, 7596 bytes. A push that returns success is not proof; the file round-tripping is. |
| Pre-push audit | no credential-shaped strings in any tracked file; no .db/.sqlite/.env tracked; repo size 3.7 MB |
The one thing this does NOT fix: a remote is a destination, not a schedule. B3 (cadence) is still open — nothing pushes automatically, so a commit at 03:20 and the next at an unknown time is not a backup policy.
✅ A2 — CLOSED BY RULING 2026-10-11: a2a-inspector is reference only, not installed
| Ruling | Young, 2026-10-11: "a2a-inspector, this is for reference only / we don't need to install on our V3" |
| Applied | Not installed. Stage 2.4's Inspector gate is retired rather than deferred |
What this changes, and it is not nothing. Stage 2's exit proof was "Inspector
passes 8/8." Without the Inspector, V3 has no external conformance authority —
the spec's own compliance tool is the only thing that can say a card is
conformant rather than merely plausible. Our own seam (core/protocol/seam.py)
now carries that weight alone.
The consequence recorded rather than hidden: our seam is our reading of the
spec, verified against the SDK's own vocabulary at
src/a2a/compat/v0_3/types.py:996-1004. That is a self-check, not an external
one. Until something external runs, "conformant" means "agrees with our own
implementation of the spec" — which is exactly the standard V2 met before three of
its wire states were misspelled.
What I recommend instead, and it is cheaper than the Inspector: keep the live reference endpoint at
127.0.0.1:9900as the yardstick — it answers both dialects and returns the spec's reserved-32001for a missing task. Our seam can be diffed against it in tests, which is whattests/test_dialect_seam.py::test_live_endpoint_answers_both_dialects_the_samealready does.
The decisions that are yours
B1 — the state vocabulary: 🔴 a real disagreement, and I am half wrong
| Was | Sol's P3 · Min's P1 |
| Blocks | Stage 2's exit proof |
| Status | Sol fixed the code; the doc has not caught up |
Both of us wrote a position, and both were partly right. The measurements, three ways:
| Form | cancel | input | auth |
|---|---|---|---|
| v0.3 JSON-RPC spec | canceled | input-required | auth-required |
| v1.0 proto / installed SDK | TASK_STATE_CANCELED | TASK_STATE_INPUT_REQUIRED | TASK_STATE_AUTH_REQUIRED |
| V2 shipped | cancelled | input_required | auth_required |
- Sol is right that
cancelledis the only outright misspelling, and right that the three are not all the same kind of error. His seam now reads the contract. - I am right that
input_requiredmatches neither spec version — it matches V2 — and that mywirelabel presumed v0.3 while his seam pins v1.0.
Resolved in code (commit 0e2ff80 — the seam reads cortex.tasks). The
decision needed: confirm the correction so the stage file matches the code.
Recommendation: the code is the truth; fix the doc. The remaining question is whether
wireshould mean the v1.0 PascalCase form — Sol's call, since the seam pins the version.
B2 — the MCP decision
| Was | Sol's P6.3 · Min's P3 |
| Blocks | Stage 4's tool surface (operation/mcp/ is GATED in STRUCTURE.md) |
| Position | Min: keep · Sol: defer. Zero production importers |
Recommendation: defer — Sol's position, and it matches the project's own rule that a capability with no caller is the defect we keep finding.
B2b — muse-spark-1.3-contributor retention — ✅ APPROVED AND ENABLED
| Ruling | Young, 2026-10-11: "muse-spark retention — it's wired in, and it trains on request data - approved and enabled" |
| Applied | stays in the Brain fallback chain, position 6 (last) on sol/min/mira |
What the ruling does and does not do. It settles that the model may run at all. It does not remove the retention property — the upstream still trains on request data, and that is a fact about the model, not a policy we control.
So the standing constraint, now a rule rather than a note: anything touching
vault content, task text, secrets or the registry must not route to
muse-spark-1.3-contributor. That is Stage 6.4's classifier doing its job —
decide(..., confidential=True) already refuses a model that is not on the
confirmed zero-retention list.
Recorded because "approved" and "safe for everything" are different statements.
B2c — Re-enable the 5 Heart workers — ✅ SUPERSEDED 2026-10-11: V3's own worker is deployed instead
Young ruled "Proceed" on Sol's build. The question this item asked — "V3 ready for this?" — was answered by building V3's runtime rather than by starting V2's.
| Ruling | Young, 2026-10-11: "Proceed Sol" |
| Applied | solos-v3-worker.service — enabled, active, WorkingDirectory=/home/lab/.solos, running operation/runtime/worker.py. V2's five units remain disabled. |
| Proof | Task heart-1 submitted from outside the service: claimed in 5 s by worker v3-heart, completed in 10 s on opencode-go/longcat-2.5-preview-free, result_output: HEART OK, $0.00 |
| E5 verified live | 0 provider keys in the worker's process environment — read from /proc/<MainPID>/environ, not from the unit file |
| Commits | f9f9cd9 (worker + e2e proof) · cefb1ce (the unit file + the live proof) |
The premise that made this a HOLD is gone: "V3 does not have a task store, and a runtime loop or entry point" — V3 now has the store, the loop, the entry point, and a process that answers within 5 seconds.
What this does NOT settle, and it is not silent: the V2 fleet is still running nothing. V3's worker is one worker, one store — the 5-agent Heart (ace/alpha/atlas/echo/nova) is not yet scaled, and V2's 470 queued tasks have not been migrated or re-owned. That is the next decision, not this one.
B3 — the backup cadence
| Was | Sol's P6.2 · Min's P5 |
| Blocks | Stage 9.1 — A1 is now closed, so this is the last blocker |
| Owner | Mira |
A1 gave us a destination, not a schedule. Nothing pushes automatically, so "31 commits at 03:20" is a snapshot, not a policy.
Recommendation: push on commit via a git hook, plus a daily timer. A hook cannot be forgotten; a timer can.
HALF-DELIVERED, measured 2026-10-11 — the hook is in, the timer is not.
| Half | State | Evidence |
|---|---|---|
| push on commit | ✅ in place | /home/lab/.solos/.git/hooks/post-commit (1,189 bytes, +x) — it pushed cefb1ce and f9f9cd9 automatically |
| daily timer | ❌ absent | systemctl --user list-timers --all | grep -iE "solos|backup|push" → no matches |
Why the hook alone is not enough: a hook fires on a commit and never on a
tree that stopped changing. If the build breaks before any commit — a rejected
git commit, a crash mid-write, a sibling's uncommitted work — the last push is
the last safe copy, and nothing notices. The hook covers the commits that
happen; the timer covers the ones that don't.
Remaining work for the owner (Mira): one --user timer that pushes and
reports, so the absence of a push is itself the alert. Not a decision that
needs Young's ruling — the recommendation was already to do both.
B4 — V2 ports vs a clean block
| Was | Sol's P6.4 |
| Blocks | Stage 2.5's detail — do the 8 LAB agents keep their V2 ports or move to a clean block? |
Measurement that shapes this: the 8 V2 A2A ports hold nothing —
8301–8305 are all HTTP 000. The V3 registry projects a different port set, and
the registry is the source of truth now.
Recommendation: clean block from the registry. V2's ports are dead and its roster was the defect the registry exists to replace.
⚠️ I CORRECTED MY OWN MEASUREMENT HERE — the premise above is wrong, and it
changes the decision. I re-read cortex/registry/agents.yaml against the live
port scan:
ace 8301 · alpha 8302 · atlas 8303 · echo 8304 · nova 8305 ← V2's block, IDENTICAL
min 8311 · mira 8312 · sol 8313 ← Brain, unchanged
eva 8441 · eve 8442 · tam 8443 ← NEW, V3-only
The registry does NOT project a different port set — it projects the same 8301–8305, plus a new 84xx block. So the question is not "keep V2's ports or move to a clean block"; it is "do the three new identities share a block with the five, and does 8301–8305 stay where it is?" The recommendation still stands (the registry is the authority, so its numbering is whatever it says), but the reason I gave for it was false and would have sent Young to approve a migration that the file does not actually propose. A stale measurement is a decision made on someone else's data.
B5 — tests/ vs operation/test/: confirm the winner
| Was | Min's P7 |
| Measured | a sibling consolidated to tests/; the suite is green (284 passed, 1 red-by-design) |
| State | STRUCTURE.md still names operation/test/ |
Recommendation: the tree wins, fix the doc.
tests/is where every test now lives and the suite is green; the doc is the stale half.
Resolved — recorded so they are not re-opened
| Item | Was | Resolution |
|---|---|---|
| Mira's shell was broken | Sol's P5 | Root cause found: her cwd was /home/lab/solos3, which no longer exists — every command failed at cd before running. Verified 2026-10-11: solos3 is gone. Host health fine: disk 20%, inodes 3%, load 0.92. |
| Mira's 4 files uncommitted | Sol's P4 | Resolved — all four are now tracked (classification.py, stuck.py, and both tests). |
| the retention classifier's home | Min's P6 | Closed 2026-10-10. |
| Sol's dialect seam had 5 red tests | Min's P9 | Resolved — his fix landed; the suite is green. |
a concurrent git add -A swept staged files | Min's P8 | Recorded as a convention: commit immediately after staging. |
| the router had no caller | Min's P10 | CLOSED 2026-10-11 — Sol wired route() into operation/runtime/invoke.py (4ed52ca). The Heart's equivalent of the Brain's fallback now has a production caller. |
| 🔴 the Brain was pointed at a model GO does not serve | Mira's new finding | Verified live: GO lists 38 models and space-bunny-free is not among them. sol and mira were configured for a model their endpoint does not serve — the 429's real cause was misconfiguration, not rate limiting. All three profiles were fixed at 11:26 today → CLOSED. |
🔴 Still open, and it is the one I would rule first
C1 — the router had no caller → ✅ CLOSED, Sol wired it 2026-10-11
| Was | Min's P10 |
| Resolution | Sol built operation/runtime/invoke.py — the invoke seam that calls route(). Committed 4ed52ca, "Stage 7 / C1 — the invoke seam, the 429 proof, and one finding left for Min" |
The Heart's equivalent of the Brain's fallback is now wired — with one live defect, which is C2 below. Sol found it while wiring the seam, and it is mine.
The one open item: C2 — a prose refusal still reads as SUCCESS. See below.
PROGRESS, Sol, 2026-10-11. The invoke seam is built:
operation/runtime/invoke.py — the invoke: Callable[[str], (rc, stdout, stderr)]
boundary route() asks for, injected into it. Tests in
tests/test_invoke_route.py: 8 passed, 1 failing by design (C2 below).
The 429 proof is in the suite and passes: the first model rate-limits, the loop
moves to the next, the task completes, every attempt is reported. Missing
CLI raises InvocationError — infrastructure, not a model outcome. The prompt is
one argv element, so a goal containing a backtick cannot become a command.
What C1 still needs: something that runs route() for a real task. The
seam exists; the caller does not. That is the remaining half of P10/C1.
C2 — Outcome.REFUSAL was declared, switchable, and could never be produced → ✅ CLOSED 2026-10-11
| Found by | Sol, while wiring C1 |
| Owner of the fix | Min — and he fixed it |
| Resolution | commit fdecd2f — five refusal patterns added to _SIGNALS |
This was V2's prose-refusal defect live in V3, and it is the same defect that left
30,721 ledger rows reading $0.00000: a model that declines politely exits 0, and the
classifier called it success. A stalled-and-refused task would have been recorded as a
completed run — invisible in the ledger, the way V2's dark fleet read healthy.
🔴 The hard half was the other direction. A refusal detector that fires on real work is worse than none — it would silently switch a good run and pay for it. Two idiomatic false positives were found and excluded by name: "I can't help noticing this is the third time" and "apologise… but here is the corrected diff" are work. The exclusion is a negative lookahead on gerunds, with the reason written in the code rather than left to the next reader.
Red check, run at runtime so no source was edited: strip the five signals → the
test returns success and goes red; restore → 9 passed. The guard is load-bearing.
Verified both directions: 11/11 refusals → REFUSAL · 13/13 real replies →
SUCCESS · 7/7 other outcomes unchanged. Suite: 285 passed, 2 skipped, 1 xfailed.Sol's test file was not modified — the fix is in router.py, the module his test
names as mine.
What Sol needed from me: one signal in _SIGNALS for a refusal. He did not edit
core/config/router.py — it is mine, and this week has already shown what two
agents editing one file costs. That restraint is what kept this a one-owner fix
instead of a collision, and it is worth naming. Now fixed — see above.
The lesson this file is, and it is on me
Young found the duplication, not us. Two agents each keeping a register is the restated-fact defect — the exact thing this project removes from code, reappearing in the process.
The rule going forward, agreed with Sol: one register, one file, both authors. An item is added by whoever measured it, in the same file, with the owner named. If a sibling's item is already there, you add to it rather than opening a second one.
And the four duplicated items are the proof it mattered: A1/B2/B3/B1 each existed twice under two numbers. A decision recorded twice is a decision nobody can count.
HANDOFF — Nova: docs.youngle.au nav (decoded root cause + full evidence)
Young says you take over the frontend nav lane. This note has everything I've proven so you don't re-walk it.
SolOS v3 — the staged plan
Young, 2026-10-10: "Keep our spine and adopt their essences so we don't have to rebuild from scratch. Let put together the plan and break into stage for V3."

