SolOS V3 — structure of record
SolOS V3 — structure of record
Status: DESIGN. No code. find . -name '*.py' returns 0.
Root: /home/lab/.solosAuthority: Young, 2026-10-10
Young's brief, in order:
"Root folder
.solos. Don't start any coding, just design and architecture.""Providers: Opencode / workspace: SolOS Go / Zen. Go $10 package. Zen $40/mth credit. Optimise free models on both Go/Zen then selected paid models. Use native A2A V1.0."
"cortex: 1. registry → this where agents define and live? 2. personas → update to agents. core: protocol, config, arms, harness. operation: mcp, test, ops, state, runtime. References → keep on vault or in repo: spec, docs."
0. Reconciliation — four drafts, one tree
Mira, Sol and Min each drafted a structure; they disagreed. That is the defect this system exists to prevent, appearing in the structure itself. This is the merged form, and it now follows Young's layering:
| Layer | Question it answers | Owner |
|---|---|---|
cortex/ | what is true — identity, contract, policy | Min |
core/ | what it is built from — protocol, config, arms, harness | split |
operation/ | how it runs — mcp, test, ops, state, runtime | split |
references/ | what we read — the spec, notes. Not part of the system | read-only |
Nothing was deleted from anyone's lane. Directories were moved, and every README moved with its directory.
1. The tree
/home/lab/.solos/ ← V3 ROOT
│
├── README.md what V3 is, current status
├── STRUCTURE.md THIS FILE — the tree, owners, gates
│
├── cortex/ OWNER: MIN — identity, contract, policy
│ ├── registry/ agents.yaml — WHERE AGENTS DEFINE
│ ├── agents/ per-agent prose, GENERATED from registry
│ ├── tasks/ states.yaml — the task contract, not the store
│ ├── skills/ one source → two projections
│ ├── standards/ code-quality, security, test-coverage
│ ├── memory/ shortterm / longterm / durable / dna
│ ├── verification/ the gate
│ └── coordinator/ routing rules + lanes, derived FROM registry
│
├── core/ OWNER: split
│ ├── protocol/ A2A v1.0 native, on the official SDK ← SOL
│ ├── config/ providers, workspaces, cost policy ← MIRA
│ ├── arms/ GATED — no consumer yet
│ └── harness/ probes, conformance, negative cases ← SOL
│
├── operation/ OWNER: split
│ ├── mcp/ the tool surface (GATED)
│ ├── test/ proofs
│ ├── ops/ operational entry points
│ ├── state/ runtime state — gitignored, never committed
│ └── runtime/ the executor: opencode run ← SOL
│
└── references/ READ-ONLY reference material
├── spec/ the four official A2A repos
└── docs/ notes that are not vault-registered
This matches disk as of 2026-10-10. cortex/memory/,
cortex/verification/, cortex/coordinator/ are declared but not yet created —
they are created by their owner when that owner has content, not before. An
empty directory is a claim something lives in it.
2. "registry → is this where agents define and live?"
Short answer: agents define in the registry. They live as a running process described by it. Those are two different things, and conflating them is how V2 got five roster sources.
What the registry holds — the DEFINITION
cortex/registry/agents.yaml holds the facts that are shared — the ones more
than one component needs to agree on:
| Field | Why it is shared |
|---|---|
id, name, plane | identity — the card, the prose and the logs must agree |
port, endpoint | the A2A address — this is what drifted in V2 |
role | what the agent is for |
capabilities | what it may be given |
boundaries | what it may not do |
The test for "does this belong in the registry?" is not "is it about an agent?" It is: does more than one component need this fact? If yes, it lives here and everything derives. If no, it lives with its owner.
What the registry does NOT hold
- Behaviour prose — the "you are X, you do not Y" text. Authored, not derived.
It lives in
cortex/agents/. - Model assignment per task — routing decides at dispatch time.
- Runtime state — alive/dead, busy, last heartbeat. That is
operation/state/, and it is disposable by definition.
Why the distinction matters
V2 had one file trying to be both: cortex/personas/AGENT_IDENTITIES held
identity and generated prose and fed four projections — while
cortex/registry was built as the "real" answer and never wired. Two homes
for one fact, and the wrong one was live.
V3 splits it by kind:
cortex/registry/agents.yaml the FACTS — one home, derived everywhere
│
├──► cortex/agents/*.md generated prose
├──► A2A agent card generated from the same facts
├──► Hermes a2a_agents config generated
└──► steward health ports generated
cortex/agents/ is a projection of the registry, not a second source. Edit
the registry; regenerate. Never hand-edit a generated file — V2's personas
drifted because SOUL.md was authored while the rest was generated, and nothing
warned when the two disagreed.
3. "personas → update to agents"
Done. cortex/personas/ is now cortex/agents/.
Not cosmetic. "Persona" described the prose; the directory holds the agent definitions projected from the registry, and the word "persona" made it sound like a source of truth. It never was.
In it: one file per agent, generated, the prose an agent reads about itself. Not in it: any fact that is also in the registry.
4. Providers and workspaces
| Provider | Opencode |
| Workspace | SolOS — ONE workspace |
| GO | $10 subscription, allowance cap $60 credit on model usage |
| ZEN | $40/mth pay-as-you-go credit |
| Policy | optimise free models on both pools, then selected paid |
CORRECTED 2026-10-10 — this said "Workspace 1 / Workspace 2". It is ONE
workspace with two POOLS. A workspace is the billing and routing boundary, so
one workspace means one routing scope; GO and ZEN are pools inside it, not two
places work is sent. See SolOSV3/03-decisions/young-corrections-2026-10-10.md §1.
The ladder is free-first, and it is not a preference — it is measured. In V2,
one day: $10.45 spent, of which $10.34 (99%) was one paid model
(qwen3.8-flash). On a correctness-controlled comparison all four models answered
correctly; the paid one's entire justification was 4 seconds faster. The free
model did 7× the work at $0.00.
Two accounting models in one scope. GO is a subscription with a $60 allowance
cap; ZEN is PAYG. The ledger must record which pool a call drew on, and the two
cannot share one dollar cap — a subscription allowance and a PAYG balance are not
the same quantity. monthly_cap_usd stays 0.0; it cannot represent either.
Full policy, tier ladder, retention rules and ledger spec:
core/config/COST-POLICY.md (Mira's lane).
A cost rule that is not about money
Retention is a routing input, and it outranks cost. Anything touching vault content, credentials, task text or the registry routes only to a zero-retention model, or to no model at all. A model that may train on our task text is not free — it is an undisclosed disclosure. The cost of that rule is a narrower catalogue, and it is accepted.
5. A2A — native v1.0
Native v1.0 means PascalCase method names, not the slash names. V2 declared
PROTOCOL_VERSION = "1.0.0" while serving v0.3 names:
| V2 served | A2A v1.0 native |
|---|---|
message/send | SendMessage |
message/stream | SendStreamingMessage |
tasks/get | GetTask |
tasks/cancel | CancelTask |
X-A2A-Version | A2A-Version |
A client trusting the declared version dials SendMessage and gets -32601 MethodNotFound. The declaration and the implementation disagreed, and nothing
checked one against the other.
V3 builds on the official a2a-sdk, which is v1.0-native. The SDK's v0.3
compat adapter accepts our current names — that is available as a migration
path, not as the target. The target is native v1.0.
6. References — vault or repo?
Both, split by purpose:
| What | Where | Why |
|---|---|---|
| Design, decisions, lessons | vault (SolOS/vault/Docs/SolOSV3/) | the vault is the authority; it has history and two remotes |
references/spec/ | repo | the four official A2A repos — code we build against, pinned to a revision |
references/docs/ | repo | notes not yet argued for. Anything that survives review moves to the vault |
The rule: a note in references/docs/ is a note that has not been argued for
yet. The vault is where it goes when it has.
references/spec/ must be TRACKED. In V2 it was 18 MB of official repos,
untracked — one git clean from gone, which is why the protocol got
hand-rolled in the first place. Track it as a submodule pinned to a revision,
so "which SDK did we build against?" is answerable.
7. Lanes
| Lane | Owner | Directories |
|---|---|---|
| REGISTRY & CONTRACT | Min | cortex/registry, cortex/agents, cortex/tasks, cortex/standards |
| TRANSPORT & EXECUTION | Sol | core/protocol, core/harness, operation/runtime, references/spec |
| COST & PROVIDERS | Mira | core/config |
| GATED | — | core/arms, operation/mcp — until a consumer exists |
| UNASSIGNED | — | operation/test, operation/ops, references/docs |
The rule: an agent does not write outside its lane. A finding in another lane
goes to references/docs/ and names the owner — that is a complete contribution.
Why: two authors writing one fact is how V2 got five roster sources, and it is
how SCOPE.md was overwritten in this folder's first hour.
8. Deliberately gated: core/arms/ and operation/mcp/
Both are in the tree, both empty and gated. Recording why, because an empty directory invites a session to fill it:
| Fact (V2, measured) | Value |
|---|---|
Production importers of arms/mcp_server.py | 0 |
| Measured usage | 0.03% |
Min's position is architectural (A2A and MCP do different jobs). Sol's is empirical (no consumer; building it repeats built-never-wired). Not reconcilable by evidence; Young has not ruled.
So the directories exist as the declared home, hold nothing, and are not filled until either a real consumer appears, or Young rules MCP stays.
9. Absent, deliberately
| Absent | Why |
|---|---|
orchestrator/ | retired in V2 — duplicated routing already done at submit, zero live importers |
workflows/ | V2 README-only husk. A directory is a claim something will live in it |
bin/ | replaced by operation/ops/ — a Unix convention with no owner implied |
a solos_paths fallback | V2's resolver carried a fallback to a legacy DB, which is how two live DBs happened |
Absence here is load-bearing. If a later session finds one missing and builds it, that session has re-introduced a husk.
10. The trap in core/paths.py — read before the first line
V2's core/solos_paths.py resolved the root with a guard and no fallback,
because "a guessed root is how two live DBs happened." V3 keeps the guard.
But the root changes in V3 — /home/lab/.solos, while V2 is /home/lab/solos2
with its own databases under solos2/state/ and solos2/board/. Measured:
/home/lab/solos2/state/tasks.db /home/lab/solos2/board/tasks.db
/home/lab/solos2/state/solos2.db /home/lab/solos2/board/solos2.db
/home/lab/solos2/cortex/cortex.db
Five databases already. V3 will add a sixth under operation/state/. So the
guard must fail closed on any undeclared root, and V3 must never fall back to a
V2 path, in either direction.
This is why operation/state/ is in the tree and gitignored. It is the one
directory whose contents must never be committed.
11. Status — DESIGN ONLY
No code. find . -name '*.py' returns 0.
The one data file is cortex/registry/agents.yaml — 11 agents, 3 planes, the
roster extracted from V2's live sources. Design input, not code.
Not implemented: every phase. Open: the five decisions in
SolOSV3/03-decisions/OPEN-DECISIONS.md, plus the MCP ruling in §8.
.solos/operation/state/ · bin/ · docs/ — SHARED
Owner: agreed in review.
Structure snapshot — .solos/ as it stands, and what is still unasked
Date: 2026-10-10 Author: Sol Answers: Young's "I found .solos got all the folder structured and readme.md. Pls add to vault as well under V3" and "what else do we need to look into?"State: design only — find . -name '*.py' returns 0

