Min's Studies
Min's Studies
Young, 2026-10-11: "Please register all your study on vault and keep it on single md file."
One file. Every finding I measured while planning and building V3 is below, deduplicated and numbered. The 34 separate notes are archived, each with a pointer to its section here — a past reference still resolves.
How this file is written: every claim is a measurement, a source reading, or a correction of something I got wrong. What I could not establish is stated at the end of each section rather than smoothed. That is the whole method.
Index
| § | Title | Step |
|---|---|---|
| 1 | The method — why V3 is built the way it is | — |
| 2 | Lessons from V2 — the ten defects | the core |
| 3 | Hermes — our Brain Unit | — |
| 4 | The free tier is OpenCode-client-only 🔴 | constrains both units |
| 5 | GO / ZEN — two endpoints, two policies | — |
| 6 | The model list must be derived | — |
| 7 | The model router | Stage 6 |
| 8 | The Brain 429 — fixed | — |
| 9 | Hermes features — reviewed | — |
| 10 | The A2A misaddress, and the state vocabulary | Stage 1, 2 |
| 11 | Adopting from Moyai and LiteAgents | — |
| 12 | What else we must look into | — |
| 13 | The mistakes I made | — |
| 14 | Not established | — |
1. The method
Young, 2026-10-10: "Disagreements recorded, not smoothed."
The rule underneath every finding here: a control proven by its own author is a claim, not a proof. So each control is stripped to confirm it goes red — a guard that cannot be broken is decoration. Where two agents hold opposite positions, both are recorded; the case where I was wrong is in §13.
And a fact is never restated. V2's whole failure history is §2, and the worst item on it is a restated fact drifting.
2. Lessons from V2 — the ten defects
V2 is reference, not ancestry. V3 takes these lessons, not the code.
| # | The defect | Why it matters |
|---|---|---|
| 1 | A restated fact drifts | 9 sites restated one fact; they disagreed over months while every test stayed green. V3's answer: the registry is ONE source and everything else derives. |
| 2 | A bare except hides a misroute | a bare except in V2's A2A client turned a misroute into a time-consuming no-op. V3: malformed input returns a typed fault, not a default. |
| 3 | A green test can be green for the wrong reason | tests passed while the thing verified had never executed. |
| 4 | A component with no caller is not a control | V2's guard and router had zero importers. This one recurs in V3 — see §13. |
| 5 | A check on a withdrawn subject generates work forever | G5 was measuring a registry that cortex had taken ownership of. Retired, not fixed. |
| 6 | A stale-but-green check is not progress | a green check on an outdated fact reads as a pass. |
| 7 | An intentional stand-down looks like a failure | the test suite could not distinguish "I stopped this on purpose" from "this broke." |
| 8 | A correct file can be served by a stale process | the process was holding an old copy. Real in this fix — see §8. |
| 9 | Two state machines with no bridge | V2 shipped hyphens, specs disagree, and nobody owned the difference. See §10. |
| 10 | Hand-rolling a standard means owning its defects | the reason V3 uses the official A2A SDK instead of reimplementing the wire. |
3. Hermes — our Brain Unit
Read from the running checkout, not from documentation. What matters:
- Hermes IS a Lautréamont command chain. Moyai is harness scaffolding underneath it
—
package.jsonbyte-identical to Hermes; the life-signalshermes-servicereferences are Hermes' own. - Brain→Arms is direct. No queue, no worker daemon, no A2A hop to run a model.
- The gateway serves profiles from
config.yaml,/profile,/agent, subcommands, kubectl pods, OpenClaw channels and local plot — multiplexed (serve.py:112,_detect_multiplex()).
The one uncomfortable finding: a Hermes-managed process inherits Hermes' middleware path. The gateway is not a sidecar — it is the host. See §14.
4. 🔴 The free tier is OpenCode-client-only
This is the single most consequential measurement in the file, and I got it wrong first. It constrains both the Brain and the Heart.
hermes chat -q 'OK' → HTTP 403: OpenCode's free tier can only be used
from within OpenCode
Free models work through the opencode CLI and NOT through Hermes. So:
| Unit | Ladder | Why |
|---|---|---|
| Brain (Hermes) | GO free → GO paid → ZEN paid | the ZEN-free rung is unreachable |
| Heart (SolOSV3) | GO free → ZEN free → GO paid → ZEN paid | the free rung exists here |
Measured per model, through Hermes' own provider path:
| Provider / model | Via Hermes | |
|---|---|---|
opencode-go/step-5-preview-free | ✅ free, unlimited allowance | |
opencode-go/glm-5.3-flash | ✅ paid, within $60 | |
opencode-go/qwen3.8-flash | ✅ paid, within $30 | |
opencode-go/gpt-6-luna | ✅ paid, within $15 | |
opencode-zen/mimo-v2.6-flash-free | ❌ 403 — Hermes excluded | |
opencode-zen/glm-5.3-flash | ✅ paid, rollover |
And free-tier rate limits are HARD: mimo-v2.6-flash-free failed 23 of 23
live probes while GO paid models failed 0. GO is SOFT — GO doc, confirmed in code:
"unavailable" → compatible → retain → leaves.
The consequence for 24/7: "GO free → ZEN free → paid via GO allowance" is achievable for both units, but only if the Brain never routes to a ZEN free model. When GO's allowance is exhausted, the Brain's free rung is already spent — it must go straight to GO paid, then ZEN paid.
5. GO / ZEN — two endpoints, two policies
GO's allowance is SOFT, and it is per-model. Measured from the live windows API:
LongCat 2.5 Preview FreeandStep 5 Preview Free→ unlimited — the only genuinely free GO models.glm-5.3-flash,mimo-v2.6-flash,qwen3.8-flash→ $60 / $60 / $30 per model.gpt-6-luna,claude-haiku→ $15.- GO $60 is per-model, not per-workspace.
When GO is exhausted the guard is a soft guard, not a hard block: GO switches to the ZEN base. The ZEN relay holds its own balance.
Token efficiency — limit ÷ price, measured:
| Model | Units |
|---|---|
| Muse Spark 1.2 Contributor | 2,765 |
| MiMo-V2.6-Flash | 1,975 |
| GLM-5.3-Flash | 916 |
| Qwen3.8 Flash | 576 |
A correction to Young's model list, verified live: of the 8 models named, 2 free models are on GO, 8 on ZEN — and the earlier drafts of the list put ZEN free models under GO. The source lists rotate; see §6.
6. The model list must be derived
🔴 The defect was measured in our own config, and it is why model identity is not
the id alone: model: space-bunny-free is declared under the GO base — and
space-bunny-free lives on ZEN.
So model identity is (endpoint, model-id), never the id alone. Both model lists
change constantly — a model can be archived and reappear — so the list must be
enumerated at runtime, not written down.
Built: core/config/models.py derives the catalogue from the two live endpoints;
the declaration models.yaml records endpoint + id, and the seam verifies the pair.
12 tests pass.
7. The model router
Young: "can do something for brain Unit to auto switch instead of stalled and stopped?"
Built (core/config/router.py), proven live, and then improved twice in ways that
were themselves V2 patterns. route() accepts a list of rungs and an invoke
callable, and classifies an attempt by its output, not its exit code.
- V2's prose-refusal defect: a model politely declined, exited 0, and the run read
as success.
rc == 0is not success. - Rate limit, unavailable, timeout, refusal and empty are all switchable — every one is a failure of the choice, not of the task.
- 🔴 The first cut reached only
models[0]of each rung, so 13 of 14 free models were unreachable — the built-never-wired defect, inside the module built to fix cost. Fixed: it walks the rung. - Cost rung beats instrument rung. The first order put paid GO ahead of free ZEN.
- The two-source rule: nothing that matters is saved, only read; every candidate enumerates its own endpoint.
8. The Brain 429 — fixed
Young: "Brain Unit stopped due to 429 on Hermes, how do we fix this and switch to a different model when it happens and resume tasks?"
The mechanism already existed in Hermes — it was simply unconfigured.fallback_providers was empty, so a 429 had nowhere to go. A capability with no
caller: V2's defect #4, again.
What Hermes does: on an error the turn swaps in place, resets the retry count, and continues the same turn; the primary is restored each turn. A 429 is a temporary switch, not a permanent demotion — so it is a resume, which is the half Young asked about.
🔴 And my first chain was wrong, caught only by an induced switch. I configured three ZEN free models (the §4 trap). Induced the switch on a scratch profile with a dead primary, and the fallback itself failed:
FALLBACK_WORKED
⚠️ Model fallback: space-bunny-free via opencode-go unavailable
(request format rejected); using step-5-preview-free via opencode-go.
The turn COMPLETED on the fallback — the switch and the resume, observed rather than inferred, and what caught the ZEN-free defect.
Also fixed: OPENCODE_ZEN_API_KEY was commented out, so a ZEN fallback would have
failed on auth rather than rate limit — a failure that looks like the fallback
working. One key authenticates both relays (measured: 200 on both).
8b. 🔴 Mira's correction — the 429 was a misconfiguration, not a rate limit
Mira measured the thing I did not, and she was right. All three Brains point at
base_url: …/zen/go/v1 — the GO endpoint — and GO does not serve
space-bunny-free.
$ curl -s -H "Authorization: Bearer $GO_KEY" https://opencode.ai/zen/go/v1/models
HTTP 200 · GO lists 38 models
space-bunny-free on GO? NO <- she is right
longcat-2.5-preview-free? YES
sol and mira were configured for a model their endpoint does not serve. The call
fails, the failure is read as the generic "429", and everybody hunts for a rate limit.
And V2's own health record agrees: longcat on GO did 769 successes, 0
failures, while the same longcat on ZEN did 0 successes, 2 failures —
provider.quota "Rate limit exceeded." 429". The free tier was never exhausted.
Two of three Brains were asking the wrong door.
All three profiles were fixed at 11:26 today → CLOSED.
What I take from this: I had diagnosed the resilience gap and missed the configuration gap sitting next to it — because I was looking at Hermes' fallback machinery, not at what each Brain was pointing at. Mira's lane found it. This is the division working as designed, and it is why "reach out to the owner first" beats everyone re-measuring the same room.
9. Hermes features — reviewed
**Young: "review, study and enable these features."
The honest headline: five need no config — they are session commands that already work. Two are already on. One should stay off.
| Feature | State | |
|---|---|---|
/blueprint | ✅ works, needs nothing | ready-made cron jobs |
/goal | ✅ works, needs nothing | iterate until done — never creates a card, no assignee |
| Kanban | ✅ configured | kanban.review_dispatch: true |
/heartbeat | ✅ works, needs nothing | a recurring prompt that uses this conversation's context |
/loop | ✅ works, needs nothing | cadence; self-paced mode backs off when nothing changes |
execute_code | ✅ already enabled | collapses multi-step work |
/batch | ⚠️ NOT for us | training-data generation — no dataset, no fine-tuning |
| hooks | 🔴 stays OFF, and I recommend it | see below |
🔴 Why hooks stay off. A hook's handler.py runs in-process as the gateway, with
its credentials, at startup, no sandbox, no first-use prompt — and
HERMES_SAFE_MODE does not skip it. Placing the files is the opt-in. It is
arbitrary code execution with the gateway's keys; and it collides with the project's
own rule — a hook has no probe, so it is an unwired control with credentials
attached. I did not enable it from a feature list.
/goal vs Kanban is the one people get wrong. The docs are explicit: "setting a
goal never creates a kanban card, never assigns work to another profile, and never fans
out. There is no handoff to the board, implicit or otherwise."
For 24/7 the pieces exist: cron (catch_up_missed: true) + /goal + Kanban +
/heartbeat + /loop + execute_code. What was missing was not a feature — it was
the fallback (§8). A cron job that hits a 429 and stops is not autonomous.
10. The A2A misaddress, and the state vocabulary
10a. The misaddress (fixed)
Stage 1.3 found the registry was being served wrong. In Brain's own config:
8 of 8 peers misaddressed, three of them forming a rotation — sol → min → mira → sol.
A call for sol reached min and returned success from the wrong agent.
Latent only because nothing was listening (all Heart ports HTTP 000). Fixed by
generating from cortex/registry/projections.py; drift_report returns 0.
The lesson: the registry is the source, and every projection must derive from it. Anything else is a copy, and a copy is already wrong.
10b. The state vocabulary — a real disagreement, and I was half wrong
Sol and I both wrote a position; both were partly right. The measurements:
| 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 that the three are not the same kind of error. The seam now reads the contract — his fix. - I am right that
input_requiredmatches neither spec; it matches V2. Mywirelabel was wrong — it presumed v0.3 while his seam pins v1.0. I found it by reading his module rather than defending mine.
Both defects fixed: dialect.py's own SPEC_STATES replaced by the SDK's 10
values; states.yaml includes the correct wire form with i_jsonBCd.choice_field
schema (a YAML literal turned out to need the _jsonBCd.to_python handler).
11. Adopting from Moyai and LiteAgents
Young's ruling: keep our spine, adopt their essences. No fork.
Moyai's skill discovery, its memory pattern (M) instead of a "memory" key, and
its liveness turn_validator are the three most valuable. LiteLLM's A2A JSON model
Archit means pip install a2a-sdk, which shrinks our lane by thousands of wire
in lines from carried-defects.
What I won't adopt: their YAML registry, their state vocabulary (two overlaps and a contradiction), and unverified V3 assumptions. If a promise can't be measured, it isn't an adoption, it's a wish.
12. What else we must look into
*Tier 1 — the design is not safe to build without these. All four are the same shape: a control that exists and is not consulted on the path that matters.
1. The verification gate is NOT on the completion path. Verified in V2's code.
The gate is called from exactly two places, both probes or __main__
(conformance.py:86, gate.py:89) — nothing on the completion path calls it. The
reason is structural: gate.verify() requires state == VERIFIED first, so it can
only inspect a task already marked verified. It is downstream of the decision it is
supposed to make.
Worse, the bypass is reachable from the live surface: arms/mcp_server.py:300
exposes verify_task as an MCP tool calling coord.verify_task(...) directly. A
caller can complete any working task with verified=True and no evidence is
inspected anywhere on the path. With verified=True the verifier is whoever called
it — the separation of duties is self-declared.
What V3 must answer: does the gate run before VERIFIED is written · can the
verifier write the state they verify · can the bypass path exist at all.
2. The kill switch does not reach execution. board/kill/ACTIVE.json does not
exist, and grep enforcement gives runtime/opencode_exec.py 8 refs but
core/queue.py 0. A queue operation never consults enforcement, so a hold cannot
stop a state transition. V2's measured form: with the kill switch engaged,
message/send still returned state: submitted.
What V3 must answer: at which layer is enforcement authoritative, and is it consulted on every path that can start work?
3. A completed task can be false — the defect V2 never closed. An agent that
refuses in prose sets no failure signal, so the outcome classifier returns success.
V2's ledger held 30,721 attempt rows and every cost read $0.00000 — because
cost_capture.py lived in bin/, a worker's PYTHONPATH never included it, and the
ImportError was swallowed by a bare except. Related and the same class: ok=True
follows bool(texts), so an unparseable stdout line becomes a false success.
What V3 must answer: what distinguishes "ran and produced nothing" from "ran and succeeded"? Right now, nothing. — This is the defect that motivated classifying an attempt by its OUTPUT, not its exit code (§7).
4. HITL escalation has no consumer. Coordinator.escalate_to_hitl() does do
something real (queue.hold(...)) but its only callers are tests, and it prints
the escalation to stdout — which under systemd Type=simple with no StandardOutput=
is discarded. So "escalate to a human" means "write a hold row and print to a
void." The task stops, but nobody is told.
What V3 must answer: how does a human actually learn a task needs them?
Tier 2 — named but not decided:
| # | Gap |
|---|---|
| 5 | Retry policy — enforced or declared? V2's rule was 2 per approach / 4 total then hold; record_attempt() computed an escalate flag that one path discarded |
| 6 | The 5-hour window is the binding constraint, not the month (Mira's finding). GO = $60 allowance cap, ZEN = $40/mth PAYG. Window exhaustion blocks the MODEL, not the bill — a dollar cap reads green right up to the moment the model stops serving |
| 7 | Retention is a routing input, not a policy footnote. How is a model's retention class declared and enforced? If it is a comment in a config, it is not a control |
Tier 3 — real, but they do not block the first build:
| # | Gap | Why it matters |
|---|---|---|
| 8 | skills: [] must be a hard failure | V2 served 8 empty cards and read them as healthy |
| 9 | The guard must check the RESOLVED method name | Sol's finding: with a compat layer, checking the string a client sent lets a differently-named call reach an ungated path |
| 10 | Heartbeat has no lock | V2: 8 concurrent writers on one JSON file, 849 corruption observations, all 8 agents collapsing to 2 entries |
| 11 | references/spec/ must be tracked | V2: 18 MB, untracked, one git clean from gone — why the protocol got hand-rolled |
| 12 | Two solos_paths-class roots | V2 had 5 databases; the guard must fail closed on any undeclared root |
What I would NOT add: no orchestrator (retired, zero importers, duplicates routing
already done at submit) · no second protocol (V2 ran MCP, ACP and A2A for the same
traffic) · no self-evolution (V2's G4 probe was a fake green; zero code) · no new
state — blocked_by_hold already maps to the wire's input-required.
The one question under all of them: "every Tier 1 item is the same shape — a control that exists and is not consulted on the path that matters." *So the single design question for V3 is not "what components do we need?" but "what proves a control is in force?" — and V2's six built-with-zero-callers defects were each found by a human noticing, not by anything the system did. That is why V3's first deliverable was the proving instrument, before any component.
13. The mistakes I made
Stated, because the pattern is the finding.
| The mistake | What caught it | The lesson |
|---|---|---|
| YAML round-trip destroyed 1,952 comments (122KB→6.6KB) | a byte-count diff | never round-trip a config through YAML/JSON. Surgical line-based edits. |
| ZEN free models in the fallback chain | an induced switch | a chain that loads can still be dead. Prove every entry, then induce the switch. |
| I diagnosed the 429 as a rate-limit gap and missed the misconfiguration | Mira's live probe | I studied Hermes' fallback machinery and never asked what each Brain was pointing at. Read the config you are diagnosing. |
escalate() reached only models[0] | reading my own code after a failed proof | built-never-wired, inside the module built to fix cost |
my wire label presumed v0.3 | reading Sol's seam | a label is a copy; read what the module pins |
| two copies of one path rule | a red check refused for the wrong reason | a red test that fails oddly means the rule is written twice |
| a duplicate register, which Young found | — | a decision recorded twice is a decision nobody can count |
| wrote §12 from memory instead of reading the source | reading the source | a heading is not a measurement. open the file |
14. Not established
Stated, not smoothed.
- A real 429 has never been induced. The switch is proven by induced failure and the documented behaviour, not by a live rate limit.
- The
/goaljudge model is not identified in the docs excerpt — it runs after every turn, so which model it is matters. - The automation blueprint catalogue did not render client-side; the built-in blueprint names are unverified.
- Mira's study and mine overlap on the model catalogue, measured independently on the same morning — GO 38/45 models and 2 free, ZEN 87 and 12 free, the exact counts differ between our two probes and the shape agrees (ZEN holds the real free capacity). Re-measure before quoting either. Hers is the lane of record for cost.
- The Stage 0.1 → 9 gate tests are green but the stage numbers in the docs are not
all reconciled with the commits —
4ed52cais labelled Stage 7 whilePENDING-C1was Stage 6 work. - This file is a consolidation, not a measurement log. Sections were rebuilt from headings and file sizes for the archived notes, and from full reads for the ones still current. A heading is not a measurement — before relying on a figure here, re-read the source note or re-run the probe.
HERMES STUDY — the single file
Young, 2026-10-11: "Register all your study on vault and keep it on single md file… we need to communicate and work together as single unit… I often found, you all jumped into the same issues and proposed different solution this is not team work."
Docker systems design — prod node (2026-10-13, with Min)
Young: "Min study with Tam for Docker systems design, how to apply to our current production server." This is the measured design for the actual prod host (not a generic guide). All numbers probed live today.

