LEARNED AND PLAN
Consolidated. This note's findings now live in
../MIN-STUDIES.md§2, 3, 11. The original is kept for its measurements; do not re-derive from it.
What we learned from Hermes — and the plan
Young, 2026-10-10: "What did we learn from Hermes, What is our plan for SolOSV3? Current A2A v1.2.2"
PART 1 — What we learned from Hermes
1.1 Moyai is built on Hermes — with one honest qualification
Yes. pyproject.toml names the package hermes-workspace: "An internal
agent workspace with Hermes, Modal sandboxes, and native app connections."
Its Hermes pin 7968c72a is in our own git history.
Mira's qualification is also true and worth keeping: Moyai treats Hermes as one of seven interchangeable harnesses — it runs Hermes rather than being wedded to it. Both statements hold:
| Claim | True? |
|---|---|
| Moyai is built on Hermes | Yes — the workspace wraps it; the package is named for it |
| Moyai treats Hermes as swappable | Yes — one of seven harness entries |
What it means for us: our Brain Unit is Hermes, and a production system already treats it as a swappable executor. That is the shape V3 wants — and it is proof the shape works.
1.2 🔴 The finding that changes the plan: Hermes already ships the spine
I studied our own Hermes source. It already implements most of what V3 Stages 2–5 proposed to build — and I proved it live, not from docs.
A2A v1.0, running on this host right now:
GET http://127.0.0.1:9900/.well-known/agent-card.json → HTTP 200
| Live field | Value |
|---|---|
name | hermes-LAB |
version | 1.0.0 |
supportedInterfaces | [{"url":"http://127.0.0.1:9900/","protocolBinding":"JSONRPC","protocolVersion":"1.0"}] |
capabilities | streaming: true, pushNotifications: true |
skills | 35, built dynamically from the live tool registry |
And both method dialects, on the wire:
POST {"method":"tasks/get"} → {"error":{"code":-32001,"message":"task not found: no-such-task"}}
POST {"method":"GetTask"} → {"error":{"code":-32001,"message":"task not found: no-such-task"}}
Identical. -32001 is the spec's reserved TaskNotFound code. The plugin's
_METHOD_TABLE maps v1.0 PascalCase and legacy v0.3 slash names — the exact
dialect defect V2 died of, already solved.
The states are spec-correct: TASK_STATE_CANCELED (one L),
TASK_STATE_INPUT_REQUIRED. V2 served cancelled, input_required,
auth_required — all three wrong.
1.3 The stuck rule — Sol said nobody had a number. Hermes has three
| Mechanism | Number | Source |
|---|---|---|
| A2A orphaned-task watchdog | 300 s | adapter.py:_MIN_ORPHAN_TIMEOUT |
| Watchdog sweep period | 60 s | adapter.py:_WATCHDOG_INTERVAL |
| kanban stale worker | 3600 s | kanban_db.py:DEFAULT_CLAIM_HEARTBEAT_MAX_STALE_SECONDS |
And the handler is the honest version — complete(tid, STATE_FAILED, "[task orphaned — no reply produced]"). A task that stops is a state, not an absence,
and the state names the cause.
1.4 kanban is Young's task contract, already built
Your four requirements map onto it exactly:
| You asked for | kanban has |
|---|---|
| store information of task | SQLite board, 30+ modules |
| assign to agent | claim + kanban_heartbeat |
| completion status | todo · blocked · review · done · archived |
| follow up if stuck or blocked | heartbeat staleness → dispatcher watchdog |
And the separation-of-duties rule is enforced: "a stale or unbound worker must not terminate a run a live successor owns."
1.5 Also native, and we did not know to ask
a2a_orchestrate(capability, message, mode)— fan-out to every peer advertising a capability. This is the orchestrator, built — and V2 retired its orchestrator as a duplicate.- Per-peer identity —
A2A_PEER_TOKENS="alice:tok1,bob:tok2", and "nothing in the request body can assert identity." The credential rule, implemented. - Anti-loop —
A2A_MAX_PINGPONG_TURNS, default 5, hard max 20. - Security — injection filters ("remote peers can never reach operator slash
commands"), outbound credential redaction, SSRF-guarded push callbacks,
per-identity rate limiting, append-only
~/.hermes/a2a_audit.jsonl. - Progressive-disclosure skills + a curator for staleness/archival.
- Two-tier bounded memory + a Memory Provider ABC.
- They name their own limits — "
tasks/cancelmarks the task canceled and drops the reply, but cannot abort the live session's in-flight turn."That honesty is the culture we want.
1.6 The uncomfortable summary
We spent a day designing an A2A-native agent system, and the framework we run inside already is one. Same lesson as the fork question, one layer down.
And the one thing that stays exactly as planned is Stage 0 — the instrument that tells whether a control is in force. Because every finding in this study came from reading source, and none of it came from the system telling us.
PART 2 — ✅ Version corrected: a2a-sdk is 1.2.2
Young is right, and our design docs were behind. Verified against PyPI and the repo tags:
| Source | Value |
|---|---|
PyPI a2a-sdk current | 1.2.2 |
| Latest release | v1.2.2, 2026-10-05 |
| Recent line | 1.1.1 → 1.1.2 → 1.1.3 → 1.1.4 → 1.1.5 → 1.2.0 → 1.2.1 → 1.2.2 |
requires_python | >=3.10 |
🔴 Two different numbers — this distinction is load-bearing
The SDK version and the protocol version are not the same thing. Our docs say
"A2A v1.0" and cite a2a-sdk — but never pinned the SDK's own version. That is
exactly the V2 defect class: a version declared in one place and served from
another.
| Thing | Version | Meaning |
|---|---|---|
| A2A Protocol | 1.0 | the spec — what the wire conforms to |
a2a-sdk (Python) | 1.2.2 | the implementation of that spec |
V2's PROTOCOL_VERSION | "1.0.0" | declared — but served v0.3 slash names |
The SDK's own compatibility table:
| Spec version | Transport | Client | Server |
|---|---|---|---|
1.0 | JSON-RPC · HTTP+JSON/REST · gRPC | ✅ | ✅ |
0.3 (compat) | JSON-RPC · HTTP+JSON/REST · gRPC | ✅ | ✅ |
And the v0.3 compat adapter still ships in 1.2.2 — src/a2a/compat/v0_3/
confirmed present at the tag. So the migration is still a flag, not a rewrite.
What changed in the 1.2.x line — and why it matters to us
| Release | Change | Relevance |
|---|---|---|
| 1.2.2 | "name the task state in terminal-state errors"; SSE shutdown grace periods | error messages that name the state — the honesty rule again |
| 1.2.1 | keyset cursors for ListTasks page tokens | pagination that does not drift |
| 1.2.0 | multi-replica cluster mode; caching headers on the card endpoint; opt-in media-type validation against the card; reject a message whose contextId disagrees with its task; reject terminal-task ops with UnsupportedOperationError | cluster mode = a fleet can scale; contextId rejection = the cross-talk guard |
1.2.0's contextId rejection is directly relevant — it is the same class as
Hermes' per-context FIFO, which exists so "concurrent same-context requests can't
cross-talk."
⚠️ This sharpens the conflict, it does not resolve it
| Option | Now |
|---|---|
| Adopt Hermes' A2A plugin | 0 dependency; live; spec-correct; but it is Hermes' own JSONRPC binding — no a2a-sdk |
Adopt a2a-sdk 1.2.2 | the official SDK; REST + gRPC too; multi-replica; but duplicates a working implementation and adds a dependency |
And note what 1.2.2 offers that the Hermes plugin does not: gRPC and HTTP+JSON bindings, multi-replica cluster mode. If V3 ever wants a transport other than JSON-RPC, or more than one replica, that is the SDK's advantage.
Still Sol's lane. My recommendation stands — but the case for the SDK is stronger than it was an hour ago, and I would rather say so than defend my first answer.
PART 3 — The plan for SolOS V3
8 stages, 3 arcs. Keeps the spine; adopts essences; every stage exits on a live
probe. Full detail: 06-implementation/PLAN.md.
The three arcs
| Arc | Stages | Establishes |
|---|---|---|
| A — Foundation | 0 · 1 | the root, and the one source of every fact |
| B — The spine | 2 · 3 | A2A transport, and the task contract |
| C — The capabilities | 4 · 5 · 6 · 7 | skills, tools, memory, cost, the executor |
The stages, and what changed after the Hermes study
| Stage | Deliverable | Status after Hermes |
|---|---|---|
| 0 — root + the prover | core/config/paths.py (no V2 fallback) · a probe harness that records evidence · a built-never-wired detector | UNCHANGED — still first. Hermes has no equivalent |
| 1 — the registry | agents.yaml as THE source; every projection derived; drift test per projection | UNCHANGED — still ours. Hermes has no single-source registry |
| 2 — A2A transport | the spine, spec-proven | CHANGED → adopt. Hermes ships it live. ⚠️ SDK 1.2.2 vs the plugin — Sol's call |
| 3 — task contract | states + wire mapping + the stuck rule | CHANGED → choose. Three numbers exist (300 s / 60 s / 3600 s) |
| 4 — skills + tools | load model, tool permissions | CHANGED → adopt. Progressive disclosure + curator exist |
| 5 — memory | the memory model | CHANGED → adopt. Two-tier + provider ABC exist. Owner still needs a ruling |
| 6 — cost + retention | GO/ZEN ledger; retention as an enforced routing input | UNCHANGED — still ours. Hermes has no GO/ZEN |
| 7 — the executor | HarnessDefinition, run-scoped credential, live steering | UNCHANGED — still ours, now over Hermes' own harness pattern |
The essence ledger — 13 adopted, each assigned a stage
| # | Essence | From | Stage |
|---|---|---|---|
| E1 | Skills: keyword search → ≤5 as metadata → content only on load | Moyai + Hermes | 4 |
| E2 | Live steering — a correction reaches an agent while it works | Moyai | 3 · 7 |
| E3 | Cost ledger at the boundary — "a request limit is not a cost limit" | Moyai | 6 |
| E4 | HarnessDefinition(...) | Moyai | 7 |
| E5 | Run-scoped credential — keys stay at the relay | Moyai | 7 |
| E6 | Checkpoint boundary; interruption records why | Moyai | 3 |
| E7 | A second memory mechanism | Moyai | 5 |
| E8 | "Project runtime configuration is untrusted" | Moyai | 0 · 7 |
| E9 | Refuse to start if the tools did not load | Moyai | 0 · 4 |
| E10 | Stop a real task mid-flight, verify what remains | Moyai | 3 |
| E11 | protocolVersion pinned per agent | LiteLLM + Hermes | 2 |
| E12 | Trace-id threading | LiteLLM | 6 |
| E13 | Validated profiles; unknown field = error | LiteAgents | 1 |
And now a 14th, from Hermes itself:
| # | Essence | From | Stage |
|---|---|---|---|
| E14 | The honest scope note — "tasks/cancel cannot abort the live session's in-flight turn" | Hermes | every stage |
Why this order
Stage 0 root + the prover ← nothing can be trusted before it
Stage 1 the registry ← nothing is safe before it (V2's worst live defect)
Stage 2 A2A transport ← the interface; task states map onto it
Stage 3 the task contract ← makes follow-up possible
4 / 5 / 6 ← independent; parallel across lanes
Stage 7 the executor ← needs 3 and 1
Stages 4, 5 and 6 are independent and can run in parallel. Nothing after Stage 0 is safe before Stage 1.
What the plan does not do
- It writes no code. Every stage is a design until its proof runs.
- It does not re-plan the spine. A2A, the registry,
.solos/, GO/ZEN and the 11-agent organisation are your rulings and are assumed. - It does not fork. 14 essences adopted as patterns and specifications, in our own lanes.
- It claims no stage is done. A phase is done when its proof runs — not when the code is written, and not when a test is green.
Still needing a ruling
| # | Question | My recommendation |
|---|---|---|
| D1–D5 | the five in OPEN-DECISIONS.md | the plan assumes each recommendation |
| 1 | Stage 5's owner (memory) | Min, with Sol owning the proof |
| 2 | a2a-sdk 1.2.2, or Hermes' plugin? | Sol's call — the SDK case is stronger now (gRPC, REST, multi-replica) |
| 3 | Does V3 get its own repo? | yes — solos2 is 735 MB of V2 history |
| 4 | Who owns the V3 build? | Sol owns engineering; Min coordinates |
| 5 | spec/ — 18 MB, untracked | track it, or one git clean loses it |
Not established
- The A2A endpoint IS live and WAS probed (card, both dialects). No task has been sent — the task lifecycle, store and streaming path are read from source.
a2a-sdk1.2.2 has not been installed or run. Version and changelog verified from PyPI, the repo tags and the changelog.- kanban has not been run.
- The Hermes plugin's security controls were read, not tested.
- No probe was run against the SDK. The SDK-vs-plugin decision rests on documented capability, not measurement.

