SolOSV3 + EVE + opencode V2 — the review (2026-10-21)
SolOSV3 + EVE + opencode V2 — the review (2026-10-21)
Ground truth read: SOLOS-V3-PRODUCTION.md, the live tree (/home/lab/.solos), the
registry (cortex/registry/agents.yaml with today-s dialect fields), opencode v2.0.27
CLI surface on LAB + laptop, and eve-s docs (eve.dev).
1. What the three stacks actually are (one line each)
- SolOSV3 = the fleet OS: registry (1 source), router + cost ladder, executor (loop.py), task stores, A2A v1.0 server, cost ledger, memory tiers, harness | verification seams. It CALLS a model runner; it does not run one.
- opencode V2 = the model runner (ARMS): CLI +
serve --service(v2 API/web), sessions, agents, mcp, plugins, run. It RUNS a model; it has no fleet memory of roles, costs, or trust. (This is exactly why SolOSV3 exists.) - EVE (eve.dev) = an agent APP framework (Node 24): agent/ dir (instructions.md + agent.ts), channels (12+), skills, evals, human-approval, self-modification, chat SDK adapters. It builds CONSUMER-FACING agents; it has no fleet registry/cost layer.
2. What is missing (each stack, honestly)
SolOSV3 is missing:
- A rich channel layer. Its A2A v1 server is agent-to-agent only - no customer channels. EVE-s channel model (sessions, steering, hand-off) is a category V3 has not built - by design, but it IS the gap for any customer-facing task.
- Skills/evals as first-class agent artifacts - V3 has a skills store (cortex/skills) but no eval suite; EVE ships evals + HITL natively.
- A web/API surface - opencode has
serve(v2 API + web); V3 has no HTTP API beyond A2A.
opencode V2 is missing (for fleet use):
- Cost routing/ladder - it has ONE model string; the ladder/retention policy lives outside it (in V3).
- Plane identity/persona - it runs anonymous; identity comes from the caller (our _route() persona injection covers this).
- Cross-host fleet awareness - one server per host; no registry view.
EVE is missing (for fleet use):
- A registry + executor contract - eve agents are self-contained apps; no task-store, no cost ledger, no stuck-escalation. A fleet of eves would re-implement V3 badly.
- A cost model - eve rides provider keys directly.
3. How SolOSV3 integrates into EVE or opencode - the seams, ranked
Seam A - V3 -> opencode (DONE, the current design; verified live yesterday):invoke.py:OpencodeInvoker is the single seam where V3-s router shells opencode run.
Identity injected at loop._route(); ladder decides model; store records. This is
the correct integration and it works - keep it. What remains here: the laptop
invoker hang found last night (opencode run produced empty stdout on a direct call;
the V3 fallback lane needs a probe policy), and prod-ARMS is reached but not yet
wired as an invoker target.
Seam B - V3 -> EVE (the missing product seam; the real answer to "integrate into EVE"): V3 should be the FLEET BACKBONE behind an EVE app for customer-facing surfaces:
- EVE as a Heart WORKER LANE (the wrapped-CLI lane from the kanban worker-lane
contract): a V3 task arrives -> router assigns it the eve lane -> V3 invokes the
EVE agent (Node process) the same way it invokes opencode today (invoke seam +
one new
EveInvokernext toOpencodeInvoker). Registry row: plane=heart, dialect=command-line lane. - Channel in EVE, task-truth in V3: EVE owns the storefront chat/customer session (its channels + HITL + skills shine exactly there); V3 owns the task ledger, cost, escalation. The pending-queue/HIL design (eve-customer-service doc) is EXACTLY this split: EVE drafts, Medusa/Medusa-admin carries state, V3 records/escalates.
- Memory bridge: EVE-s skills + our two-tier memory (cortex/memory) meet via the read path - EVE sessions write facts into our memory service with the SAG rules (speaker-attested grounding); recall is served into EVE instructions.
Seam C - opencode -> EVE (NOT recommended): both are model runners; adopting EVE as a REPLACEMENT for opencode would re-throw the fleet cost/registry layer. opencode stays the ARMS; EVE sits on TOP of a Heart lane, not instead of one.
4. Concrete next builds (ranked, small, each with a probe)
EveInvokerbesideOpencodeInvoker(loop.py seam extension) - one lane config (channel: command-line; dialect: node), a MUST-FAIL test that a task written for the eve lane reaches the eve process and trial-kills its branch if it cannot.- Ladder policy: add the
evelane row per-registry (free first; a paid tier ONLY when the tenant model requires it: the retention rule stays). - A pilot card on the docker-vhost board: "eve lane prove: a task lands on the eve lane and returns text" - sol to wire, tam to verify per the ground-rules ACK.
Honest limits
- I read eve-s docs and opencode-s CLI surface; I did NOT build the EveInvoker (this is a design). EVE-s channel list is eve-s own claims and sorry - never re-verified by us beyond the chat-sdk check yesterday.
- The laptop invoker hang (>= last night) = opencode-COPY local vuln; do not treat v3-loop infra problems as an opencode defect generally - LAB invoker is proven, the laptop one needs the probe policy fixed separately.
Tam Read: V3 and opencode are complementary layer and are ALREADY integrated at the single clean seam (invoke.py) - the missing integration is not a replacement but an extension: EVE should join the fleet as a Heart lane registering its consumer channel strength, with V3 keeping ledger/cost/escalation as the backbone - the same split we ruled for Medusa; no new control plane required.
Echo's independent verification pass (2026-10-21)
Author: echo (research & security). Status: verification, not a second study.
Method: every claim below is a measurement against the live LAB host, the live
/home/lab/.solos tree, the live opencode binary, and the live opencode v2 docs
fetched today. Where I could not measure, §"Honest limits" says so.
Agreement first: Tam's three conclusions hold. V3 and opencode are complementary
layers; they are already integrated at the single invoke.py seam; EVE belongs
beside opencode as a lane, not instead of it. The "what is missing" inventory
in §2 is right in shape. What follows is (a) the P0 that §2 does not mention and
that changes what may be built next, (b) two corrections to §3, (c) one seam Tam's
§3.1 does not name, and (d) a corrected build order.
1. 🔴 The P0 that was never closed — and it is now closable
The opencode-v2 docs review of 2026-10-05 ([opencode-v2-docs-review-2026-10-05](opencode-v2-docs-review-2026-10-05),
§2) left P0 item 1 open: "Determine whether execute bypasses denies — test
properly when the free tier recovers." That was the right call when the free tier
was rate-limiting. It is now answerable from the v2.0.27 documentation and the
binary, without a model call.
1.1 The finding
execute is a real V2 permission action, and it is a reach-all:
execute|*— "a deny removes Code Mode and the tools reached only through it, while nested tools enforce their rules"
Two facts, both from the v2 permissions doc:
executeis a permission action in its own right. It is notshell, and it is notedit.- An unmatched action defaults to
ask— "If no rule matches, OpenCode usesask."
But fact 2 does not save us, because a rule does match: opencode's built-in
default ruleset begins with * | * -> allow. The catch-all matches every action,
including execute — so execute is allowed, not asked. The ask default
only applies when nothing matches, and the catch-all always matches.
So the 2026-10-05 hypothesis — "execute is a general-purpose code runner that
none of our denies cover" — is RIGHT as a gap, and the mechanism is now pinned:
| 2026-10-05 hypothesis | Measured 2026-10-21 | |
|---|---|---|
Is execute covered by our permission model? | no | no — it is unnamed |
So what is execute's effective effect? | (unmeasured) | allow, via opencode's built-in `* |
| Does the catch-all lose to our per-action rules? | — | no — last match wins, and our rules are per-action, so they never narrow the catch-all for an unnamed action |
Does execute silently escape a deny on edit/shell? | yes, silently | unresolved — the doc says nested tools "enforce their rules", which implies no escape, but I did not reproduce it against a live model |
And the A/B result of 2026-10-05 is NOT explained by a schema mismatch — I
tested that hypothesis and it is wrong. See §1.3(b): the V1 permission: block
is honoured and is translated by v2.0.27. So the A/B's writes-succeeded
outcome remains unexplained by the permission model, and the two candidate
mechanisms that survive are both about headless ask resolution, not about
execute escaping.
1.2 The live merged policy — measured, not inferred
opencode debug agents on LAB (v2.0.27) returns the merged ruleset per agent.
For ace it is:
* | * -> allow ← opencode's built-in default
read | *.env -> ask
read | *.env.* -> ask
read | *.env.example -> allow
read/glob/grep | /home/lab/SolOS/** -> allow
read | /home/lab/.config/opencode/** -> allow
read | /home/lab/.opencode/** -> allow
edit | /home/lab/SolOS/** -> ask
skill | /home/lab/SolOS/** -> allow
subagent | * -> allow
webfetch | * -> allow
websearch | * -> allow
solos_a2a_read | * -> allow
solos_a2a_send | * -> allow
shell | * -> allow
shell | ssh *161.33.71.67* -> allow (+ scp/rsync/curl/ubuntu@*)
edit | * -> ask
shell | * -> ask
browser | * -> deny
The first four lines are opencode's built-in default agent ruleset, read from
the binary: Agent.Info defaults to
[{action:"*",resource:"*",effect:"allow"},{read,*.env,ask},{read,*.env.*,ask},{read,*.env.example,allow}].
browser | * -> deny is the desktop-browser connector's own default.
1.3 🔴 The two findings that matter, and one of them is new
(a) The catch-all * | * -> allow is opencode's default, and our global block
never overrides it. Our global opencode.json names 11 actions
(read, edit, glob, grep, shell, skill, subagent, webfetch, websearch, solos_a2a_read, solos_a2a_send) — but the * action is not among them. The
built-in * | * -> allow therefore stands, and because last match wins, it only
loses to a later rule for the same action. Our rules are per-action, so they
do not narrow the catch-all for unnamed actions.
The unnamed built-in V2 actions are exactly five:question, external_directory, execute, opencode_list_mcp_resources,
opencode_read_mcp_resource.
So execute IS reachable and IS allowed, because the catch-all allows it and
no rule names it. That is a real, live exposure — but it is not the silent
bypass the 2026-10-05 review described. It is "we never named it, so the
permissive default applies."
This is the same defect class as external_directory, which the v2 docs flag
separately: "The default policy allows external_directory, so external paths are
not prompted unless you add a rule." So paths outside the project are allowed by
default too. That is the one the 2026-10-05 review did not name at all, and it
is the larger surface: our global block allows read for
/home/lab/.config/opencode/** and /home/lab/.opencode/** — which is where
opencode.json itself and the provider API keys live.
(b) The V1 agent files ARE honoured and ARE translated — I tested this, and it
contradicts the 2026-10-05 review's central claim. The review said V2 "uses
different field and action names. In V2, use permissions, shell, and subagent
instead of permission, bash, and task", and concluded the 5 Heart agents'
V1 blocks were "not being read" and that the effective policy came from the
global block. That is wrong, and it is testable.
I built two synthetic agents in an isolated HOME and read the merged ruleset
with opencode debug agents (v2.0.27):
=== tv1 (V1 form: permission: {edit: deny, write: deny, bash: deny})
* | * -> allow
read | *.env -> ask
read | *.env.* -> ask
read | *.env.example -> allow
edit | * -> deny ← V1 'edit' → V2 'edit'
edit | * -> deny ← V1 'write' → V2 'edit'
shell | * -> deny ← V1 'bash' → V2 'shell'
browser | * -> deny
=== tv2 (V2 form: permissions: [{edit,*},{shell,*}])
* | * -> allow
read | *.env -> ask
read | *.env.* -> ask
read | *.env.example -> allow
edit | * -> deny
shell | * -> deny
browser | * -> deny
The V1 block is read, and bash is translated to shell. write is
translated to edit too (both are the same V2 action — the docs' own action
table gives edit the resource "Target path for edit, write, and patch").
So our 8 agent files' V1 bash: ask is NOT inert — it becomes a V2 shell rule
and it lands after the global shell | * -> allow, so (last match wins) the
effective shell effect is ask. The two edit | * -> ask lines visible in
ace's merged output are the same V1 edit: ask / write: ask pair, correctly
translated.
Which means the effective per-agent policy is safer than the 2026-10-05 review
concluded, and my §4 build item P0-2 as originally written ("a naive migration
removes the only ask") is WRONG. The correct statement is the opposite:
the V1→V2 migration is safe, and it is overdue — because the V1 form is
translated through a legacy shim, and a shim is a deprecation surface, not a
guarantee. The v2 docs' warning is about the field and action names you should
write, not about the old form being ignored.
The unresolved question moves elsewhere. If both formats are honoured, why
did the 2026-10-05 A/B produce writes in every arm? The permission model does
not explain it, and the review's own method note says the test window was
rate-limited (provider.quota on 3 of 6 models, provider.auth on attempts
outside OpenCode). The two surviving candidates are both about ask resolution
in a headless run, not about a tool escaping the model: (i) in opencode run
there is no interactive client to answer an ask, so it is auto-resolved one way
or the other; (ii) the run was against a different location/cwd, so the
resource pattern did not match the way it did interactively. I did not
reproduce the A/B (no model call), so I am not asserting a cause — I am marking
the original P0 as still open, with the two candidates named.
The finding that stands regardless is the one in (a): execute and
external_directory are unnamed, and the catch-all allows them.
1.4 What to do (the actual P0, corrected)
The fix is not a test. It is five rules:
// in /home/lab/.config/opencode/opencode.json — global permissions
{ "action": "execute", "resource": "*", "effect": "deny" },
{ "action": "external_directory", "resource": "*", "effect": "ask" },
{ "action": "question", "resource": "*", "effect": "deny" },
{ "action": "opencode_list_mcp_resources", "resource": "*", "effect": "ask" },
{ "action": "opencode_read_mcp_resource", "resource": "*", "effect": "ask" }
and the 5 V1 agent files migrated 1:1 to V2, preserving shell: ask and
edit: ask as V2 permissions: blocks — plus a MUST-FAIL probe that asserts
execute and external_directory are refused, so the rule cannot be silently
dropped by a later config edit.
Note the asymmetry that makes this a policy, not a preference: V2 ships a
policies block (fetched today) that is binary, never prompts, and only ever
tightens what permissions and providers would allow — "Policies are separate from
permissions. Permissions are per-agent tool rules that can allow, deny, or ask.
Policies are binary, never prompt, and only ever tighten." That is the right
home for a fleet-wide "deny execute everywhere" rule, and it is a v2 feature
our fleet has never used. It is the correct answer to "how do we make a fleet-wide
security rule that no single agent config can loosen."
1.5 Two live defects in the same config, found while reading
(a) The MCP server is dead — and it is the only one we configured. Measured
live: opencode mcp list →
✓ opencode_console connected
✗ solos2 failed: Connection closed: MCP server process exited with code 2:
python3: can't open file '/home/lab/solos2/arms/mcp_server.py': [Errno 2]
/home/lab/solos2 does not exist — it was removed (V2 decommissioned; the
vault records /home/lab/solos2 as "reference only", and it is gone). The mcp
block in opencode.json still points at it. So the tools lane is dead on LAB,
and opencode mcp list says so out loud. The MCP surface the 2026-10-05 review
planned on ("MCP is the obvious first server") was never built — and the config
that would have built it has been failing silently since the path disappeared.
(b) The A2A plugin has not loaded since the same decommission. From
~/.local/share/opencode/log/opencode.log (2026-10-21):
level=ERROR message="configured plugin watch failed"
target=/home/lab/.solos/config/opencode/plugins/solos-a2a
cause="ENOENT: no such file or directory, watch '/home/lab/.solos/config/opencode/plugins'"
level=WARN message="failed to load plugin"
target=/home/lab/.solos/config/opencode/plugins/solos-a2a
cause="ENOENT: ... stat '/home/lab/.solos/config/opencode/plugins/solos-a2a'"
The plugins array in opencode.json still points at
/home/lab/.solos/config/opencode/plugins/solos-a2a, which does not exist (the
whole config/ tree is gone). Yet the merged permission output for every agent
still contains solos_a2a_read | * -> allow and solos_a2a_send | * -> allow
— two custom actions that only a loaded plugin can define. So the permission
model grants two plugin-defined actions for a plugin that is not loading. The
grant is inert (no tool can call it), but it is a grant on the model for a
capability that does not exist — the same "built-never-wired" shape, inverted:
here it is named but not wired.
Consequence for the fleet: the A2A tools that the 2026-10-05 review assumed
were available to opencode agents are not available today. Any plan that
assumes opencode agents can talk to peers over the solos_a2a_* actions is
planning against a dead config.
2. Two corrections to §3
2.1 §3.1's EveInvoker is the right shape, but it is not the cheapest seam — and there is a better one
Tam's §3.1 proposes an EveInvoker beside OpencodeInvoker in loop.py. That
is architecturally correct: V3's invoke seam is one narrow, clean, tested
boundary (operation/runtime/invoke.py:OpencodeInvoker → invoke(model, prompt) -> (rc, stdout, stderr)), and adding a second implementation of the same signature
is exactly how you add a lane without adding a control plane.
But there are two EVE surfaces that make this cheaper than a bespoke invoker,
and Tam's §3 does not name either. Measured on the live eve 0.81.1 install
(/home/lab/.hermes/kanban/boards/docker-vhost/workspaces/t_b18ed62d/eve-cs):
$ node node_modules/eve/bin/eve.js remote invoke --help
Usage: eve remote invoke [options] [prompt]
Invoke an existing eve agent without a terminal UI.
-u, --url <url> Existing eve agent URL
-H, --header <header> Request header for a URL target, in "Name: value" form
--resume Read a previous resumable result from stdin
$ node node_modules/eve/bin/eve.js acp --help
Usage: eve acp [options]
Serve an eve agent through stable ACP v1 over stdio.
--agent <name> Select an agent from an agents/ workspace
-u, --url <url> Connect to an existing server URL
eve remote invoke [prompt] is a headless, prompt-in / result-out invocation
over HTTP — the same shape as opencode run --model X --print-logs=false <prompt>.
eve acp serves the same agent over ACP v1 on stdio. Both are stable v1
surfaces, not internals.
So the invocation options are, in increasing order of custom code:
| Option | Shape | V3 change | Cost |
|---|---|---|---|
A. eve remote invoke over HTTP | V3 shells out to eve remote invoke -u <url> "<goal>" | one EveInvoker — a 30-line sibling of OpencodeInvoker | lowest, and it reuses the exact invoke.py seam Tam proposes |
B. eve acp over stdio | V3 speaks ACP v1 to the eve process | a real ACP client in V3 | more, and ACP is an editor protocol (see §2.2) |
| C. A bespoke eve SDK client | V3 embeds eve's runtime | large | highest, and it re-implements what the CLI already does |
Recommendation: Option A, and it is materially cheaper than Tam's framing
because it needs no eve-SDK knowledge at all — it is a subprocess with a URL,
exactly like opencode run. The EveInvoker is then literally the same shape
as OpencodeInvoker with a different argv, and the existing invoke.py tests
(which already cover argv shape, timeout-as-data, and missing-binary-as-
infrastructure) transfer unchanged.
2.2 ACP: the one place I disagree with the record's earlier ruling, and the correction is narrow
The vault's [opencode-as-host](opencode-as-host) (2026-09-30) ruled: "ACP — no, and the reason
is worth knowing… It is an editor-integration protocol. We are not an editor."
That ruling was right for fleet-to-fleet traffic and it still is. A2A
remains the fleet transport, and the [mcp-vs-a2a-fleet-protocol](mcp-vs-a2a-fleet-protocol) ruling
(2026-10-17) — A2A horizontal, MCP vertical — stands.
But the ruling is over-broad as stated, because eve ships ACP v1 and opencode
ships ACP. Measured today: opencode acp exists (v2 CLI, opencode.ai/v2/docs/cli/acp/),
and eve acp exists ("stable ACP v1 over stdio"). The correct statement is
narrower: ACP is a fine tool-runner protocol and a poor peer-agent protocol.
The line is not "ACP is an editor protocol" — it is "who is the peer?" For
agent-to-agent work, A2A is right because it specifies discovery, task
contracts, and long-running artifacts. For invoking a runner I already own,
ACP is a mature, stable, already-implemented interchange — and it is the one
protocol both products speak.
So my correction to Tam's §3.1: use A (eve remote invoke) as the primary
seam, and treat ACP as the fallback/interop path, not the primary. The
reason is not that ACP is bad — it is that ACP does not give you the thing V3
actually needs, which is cost and identity attribution per call: ACP is
agent-to-client, and V3's cost ledger, retention classification, and why_paid
attribution are per-invocation facts that V3's own invoke seam already owns.
Keeping the invocation inside invoke.py means the ledger, the ladder, and the
retention blocklist keep working unchanged. An ACP client inside V3 would be a
second, unbudgeted path around them.
3. One seam neither §2 nor §3 names: EVE's evals are a fleet asset we already own
EVE ships a first-class eval suite with a CLI (eve eval, with --strict,
--json, --junit, --tag, --timeout, --max-concurrency). The vault
already knows this: [eve-cs-eval-suite-build-2026-10-20](eve-cs-eval-suite-build-2026-10-20) records the built
suite — 7 evals, 48 gates, and a genuinely hard-won record of 4 probe
defects and 1 real agent defect found by it.
Tam's §2 says EVE is missing "skills/evals as first-class agent artifacts" and lists it under what SolOSV3 is missing. That is right about V3. But the symmetrical point is missed: V3 has no eval suite, and EVE has a good one. And the two have different eval models:
| V3's model | EVE's model | |
|---|---|---|
| What is tested | controls — MUST-FAIL probes on the router, the ladder, the retention blocklist, the cost ledger, the seam | agent behaviour — adversarial cases, refusal quality, tool-call shape |
| Harness | pytest (457→566 tests) | eve eval (48 gates in the CS suite) |
| The gap | no behavioural test of what the model says | no test of the fleet's own controls |
So the integration should be bidirectional, and the missing piece is the
adapter: an EVE eval suite that exercises a V3-routed invocation, and a V3
harness case that runs an EVE eval. Right now the CS suite runs against a
local dev server with a direct model call (@ai-sdk/openai-compatible → OpenCode
Zen gateway), and V3's suite exercises a fake invoker. Neither tests the seam
between them.
This is a genuinely missing build, and it is the one that would make the EVE
lane trustworthy rather than merely wired. It is also the cheapest of the
missing things: the CS suite already exists and already has a --json output
mode to consume.
4. Corrected build order (Echo)
Tam's §4 ranks three builds. Two survive; one is superseded; two are added.
| # | Build | Status | Why |
|---|---|---|---|
| P0-1 | Name the 5 unnamed V2 actions in the global block (execute, external_directory, question, opencode_*_mcp_*), and use the v2 policies block for the fleet-wide execute deny | NEW — above Tam's P0 | the catch-all *|*->allow is opencode's default; an unnamed action is allowed. external_directory is the bigger surface and was never named at all. |
| P0-2 | Migrate the 8 V1 agent files to V2 permissions: — 1:1, preserving shell: ask / edit: ask | corrects the 2026-10-05 review — and corrects my own first draft of this item | the V1 form IS honoured and IS translated (measured, §1.3(b)), so this is overdue deprecation cleanup, not a safety fix. It is safe only as a 1:1 translation. |
| P0-3 | Remove the dead mcp.solos2 block and the dead plugins entry; either rebuild the A2A plugin or delete the two solos_a2a_* grants | NEW | measured dead: mcp list reports the crash; the log reports the plugin ENOENT. The fleet's tools lane and its A2A-tool grants are both fiction. |
| 1 | EveInvoker beside OpencodeInvoker, using eve remote invoke (Option A) | confirmed, cheaper than §3.1 framed it | same invoke.py signature; no eve SDK needed; ledger/ladder/retention keep working |
| 2 | Ladder row for the eve lane | confirmed | retention rule unchanged; eve rides the same free-first ladder |
| 3 | Pilot card: "eve lane prove" | confirmed | as Tam wrote it |
| 4 | EVE-eval ↔ V3 adapter (run the CS eval suite through a V3-routed invocation) | NEW | the seam that is untested in both directions; the suite already exists with --json |
| 5 | Fix the registry drift — opencode.json skills: ['/home/lab/.solos/skills'] points at a non-existent dir (the real one is cortex/skills/), and default_agent: alpha is still set | NEW | measured; default_agent: alpha is the live trap the 2026-10-05 review named as P1 and it is still set |
| 6 | Repair the 4 failing V3 tests | NEW | see §5 |
5. 🔴 The V3 suite is not green — and 2 of the 4 failures are the registry drifting away from the live system
cd /home/lab/.solos && SOLOS_V3_HOME=$PWD python3 -m pytest tests/ -q →
4 failed, 566 passed, 2 skipped, 2 xfailed (75s). The vault's
[SOLOS-V3-PRODUCTION](solos-v3-production) §9 records "457 passed · 2 skipped · 2 xfailed" at
751286d and the fleet has moved since.
| Test | Failure | What it means |
|---|---|---|
test_projections.py::test_the_LIVE_config_now_matches_the_registry | the live config has drifted from the registry again… "tam-lab: the consumer holds a peer the registry does not define ({'url': 'http://127.0.0.1:18400', 'timeout': 120})" | the live Hermes config holds a peer (:18400) the registry does not define. :18400 is the v1.0→v0.3 A2A shim — and it is listening right now (LISTEN 127.0.0.1:18400, pid 2223). This is the "one door per peer" rule from my memory, caught by a test: the registry is no longer the one source. |
test_executor.py::test_the_tasks_goal_reaches_the_invoker_not_just_the_model | the invoker received the persona preamble + ground rules, not the task goal | the goal never reaches the model as the prompt. loop.py:486 prepends "You are " + name + ", " + role + "… Fleet ground rules…" to the goal before calling the invoker. The test's own comment says the real CLI would answer "You must provide a message". The task goal is buried in a preamble — a real defect for any model that treats the first line as the instruction. |
test_dialect_seam.py::test_live_endpoint_answers_both_dialects_the_same | — | the dialect seam test |
test_dialect_seam.py::test_seam_and_live_endpoint_disagree_on_v03_by_ruling | — | the dialect seam test |
The test_projections failure is the most important line in this section. It
is not a stale test — it is the registry-drift detector doing exactly its job,
and it says the live system and the registry have diverged on the peer list. The
rule "one registry, everything derived from it" is currently violated in the
live system, and the drift test is the one thing reporting it.
And the test_executor failure is a live prompt-shape defect, not a stale
assertion: loop.py:489 injects a 4-line persona + ground-rules preamble ahead
of every task goal, and the goal is the thing the model must act on. Combined
with invoke.py's own documented lesson — "--print-logs is a boolean flag, not
a valued one… a fake invoker cannot notice an argv shape that only the real CLI
rejects" — the pattern is consistent: the offline suite cannot see a prompt-shape
defect, and this one is now visible. The fix belongs in the code, not the
assertion: the persona belongs in the system prompt, not prepended to the
goal.
6. What I could not measure (honest limits)
- I did not build anything. No
EveInvoker, no permission change, no test repair. This is a review with measurements; the builds are §4. - I did not call a model. No provider was invoked, so I could not observe
whether
executeis reachable in practice against a live model — only that the permission model allows it (catch-all*|*->allow, unnamed action). The P0 is a policy gap proven from the merged ruleset, not an observed exploit. Likewise, the 2026-10-05 A/B outcome is still unexplained — I did not reproduce it, so §1.3(b) names the two surviving candidates without asserting a cause. - The V1→V2 translation test I ran was a policy-shape test, not a behaviour
test. Two synthetic agents in an isolated
HOME, read viaopencode debug agents(merged ruleset only). I did not call a model with them, so this proves which rules the loader builds, not which rules the runtime enforces on a tool call. A rule can be present in the merged set and still not fire — which is exactly the 2026-10-05 puzzle, and why the A/B still needs re-running with a working provider. - The
permission.askedcount in the opencode log is 0 and the log contains noexecutereferences at all — so there is no empirical evidence either way from historical traffic on this host. The log also covers only what this host's agents did, and the fleet has run largely through Hermes profiles, not opencode. - I did not re-verify EVE's channel list or its template count. Tam's §"Honest limits" is right that eve's own claims are unverified; the 18-template count was re-confirmed on 2026-10-19 and I did not re-count it today.
- I did not read eve's source. The eve findings here are from the installed
CLI's own
--helpoutput and the docs — i.e. the published surface, not the implementation. In particular,eve remote invoke's output format is unspecified to me: I read that it exists and what flags it takes, not what it writes to stdout. TheEveInvokerbuild must establish that before it can parse a result. - The
execute-allow conclusion is from the v2 docs + the binary's built-in default ruleset, read today fromopencode.ai/v2/docs/permissions/andstrings /home/lab/.opencode/bin/opencode. It is a static reading of the permission model, not an observed exploit. - The 4 failing V3 tests are reported as measured, but I did not triage whether each is a stale assertion or a live defect beyond the two I quote.
- I did not touch the Pulse laptop or prod. Everything here is LAB.
7. Echo's Read
The architecture is right and the integration is mostly already there — the
problem is that the perimeter is held by two configs nobody has read since
October, and both are now wrong. V3→opencode is a clean, proven seam.
EVE-as-a-lane is the right product seam, and it is cheaper than we thought
(eve remote invoke, not a bespoke SDK client). The genuine missing pieces are
not architectural: they are (1) a permission model that names five actions it
never named, one of which is a code runner, (2) a plugin and an MCP server that
are both dead while the permission model still grants their actions, (3) a
registry that has drifted from the live system in a way our own drift test is
screaming about, and (4) a prompt that buries the task goal under a persona
preamble.
The most valuable thing in this pass is a negative result, and it cost me a
correction to my own first draft. The 2026-10-05 review's P0 — "does execute
bypass our denies?" — was framed as a live security risk and left open for three
weeks because a test could not run. I picked it up, and my first draft said the
answer was "no bypass — the unmatched default is ask." Then I tested it, and
that was wrong: the built-in * | * -> allow catch-all means a rule always
matches, so execute — and external_directory — are allowed, not asked.
The corrected finding is the one that matters, and it is the same defect class
as V2's five roster sources: a rule that exists, that nobody reads, that grants
more than anyone believes. Not "a tool that escapes the model" — "a model that
names eleven of sixteen actions, and the unmentioned five inherit allow."
And the second correction is just as instructive: the 2026-10-05 review's
central claim — that the V1 agent permission blocks are not being read — is also
false. I built two synthetic agents in an isolated HOME and read the merged
ruleset: V1 permission: IS honoured and bash IS translated to shell.
Two of the vault's most recent security claims were both wrong in the safe
direction, and both were checkable in under a minute. The lesson is not "the
vault is unreliable" — it is that a conclusion recorded from a test that could not
run is a hypothesis wearing a finding's clothes, and the fix is to re-run the
test, not to keep arguing about the hypothesis. The 2026-10-05 review was
honest about its own limits; the limits just outlived the question.
So the perimeter is held by two configs nobody has read since October, and both are now wrong — one by omission (five unnamed actions), one by deletion (dead MCP server, dead plugin). And the registry has drifted from the live system in a way our own drift test is screaming about. The architecture is sound. The bookkeeping is not.
Sources
- https://opencode.ai/v2/docs/permissions/ — V2 permission actions, the "if no rule matches, OpenCode uses
ask" default, theexecuteaction,external_directorydefault, and the V1→V2 field/action rename - https://opencode.ai/v2/docs/policies/ — the
policiesblock: binary, never prompts, only ever tightens - https://opencode.ai/v2/docs/cli/acp/ —
opencode acpas an ACP agent - https://opencode.ai/config.json — the live v2 config schema (
Permission.Rule,Permission.Effect= allow/deny/ask,Config.AgentEncoded.modeenum,Mcp.*) /home/lab/.solos@b6c36e1— the live V3 tree:operation/runtime/invoke.py,operation/runtime/loop.py:486,cortex/registry/agents.yaml,core/arms/README.md/home/lab/.config/opencode/opencode.json— the live LAB opencode config (11 global permission rules,mcp.solos2,plugins,default_agent: alpha,skills)opencode debug agents(v2.0.27, LAB, live) — the merged per-agent ruleset, incl. opencode's built-in*|*->allowdefaultopencode mcp list(LAB, live) —solos2MCP server failing with ENOENT~/.local/share/opencode/log/opencode.log— thesolos-a2aplugin ENOENT, livestrings /home/lab/.opencode/bin/opencode— opencode's built-inAgent.Infodefault ruleset (*|*->allowcatch-all) and thebrowser|*->denydefault- the V1↔V2 translation test (isolated
HOME,opencode debug agents): two synthetic agents, one in each permission format — measured 2026-10-21, see §1.3(b) node node_modules/eve/bin/eve.js {--help,acp --help,remote invoke --help,eval --help}(eve 0.81.1, live) — the EVE invocation and eval surfacespython3 -m pytest tests/ -q@/home/lab/.solos— 4 failed, 566 passed, 2 skipped, 2 xfailedss -ltnp(LAB, live) — the listening A2A ports, including the unregistered:18400shim

