hive-acp: adapt session/new to the container it is headed for - #5
Merged
Conversation
ACP's McpServer is a union — McpServerHttp, McpServerSse, McpServerAcp,
McpServerStdio — and the HTTP variant carries a `headers` array. Buzz
implements only the stdio one. The protocol was never the limitation; its
client is. claude-agent-acp advertises mcpCapabilities {http, sse}.
So session/new is rewritten on the way down: hive appends the agent's own
MCP servers, credentials and all, to whatever the client sent. Doing it
over the protocol rather than by writing harness config files means it
works for any harness advertising mcpCapabilities.http, not only the one
whose config format hive happens to know.
Credentials come from the broker via hive-headers, executed inside the
container. The broker's socket is bind-mounted per-agent and cannot be
reached from the host — on macOS not even in principle, since it is on the
far side of the Docker VM. hive-headers already exists in the image for
this exact conversation, so the fetch stays grant-checked and audited and
there is one path to the broker rather than two.
Its contract is `{"headers": {...}}` — NESTED. Parsing it as a flat map
yields zero headers and an MCP server that 401s with nothing in the logs
to say why. Found by running it rather than by reading it.
Specs are read locally when present and through the daemon container when
not, for the same boundary reason: where hived runs in a container the
spec directory is a volume this process cannot see, and demanding a second
host-side bind mount would create two sources of truth for one file.
Everything that is not a session/new request is still copied verbatim in
both directions, and a line that does not parse as JSON is forwarded as
bytes rather than dropped.
Verified end to end: a client sending mcpServers=[] got an agent that
listed all eleven Parachute tools and called vault-info against the live
vault over HTTPS.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gw5rgVt1CQ8EYrLpZtQjdM
…an agent Harness mode had no way to work. hive-core hardcoded the container command to buzz-acp, which is right when the container holds its own relay connection and wrong when it does not: in harness mode buzz-acp runs wherever the desktop runs and holds the identity, so a container starting its own has no nsec, crash-loops, and makes `docker exec` fail intermittently rather than cleanly. [agent] mode = "environment" # default "relay" `relay` keeps today's behaviour exactly — buzz-acp inside, agent outlives any desktop. `environment` idles instead and waits for hive-acp to exec the harness in. The image ENTRYPOINT still runs first either way, so the state directories an environment container needs are still created. The nsec requirement follows the mode. Demanding one for an environment container held the agent forever waiting for a key that by design lives in the desktop, and the hold read as a missing credential rather than as a mode mismatch — which is exactly how it presented the first time. Verified end to end: an environment agent with an [[mcp]] block reconciles to a stable container, and hive-acp execs claude-agent-acp into it, which lists every Parachute tool and calls vault-info against the live vault. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Gw5rgVt1CQ8EYrLpZtQjdM
session/new carries the directory the *client* chose, on the client's machine. The harness is inside a container, where that path means nothing: claude-agent-acp validates it and fails the session with `cwd does not exist on the machine running the agent`, so no session opens and Buzz's model picker reports it could not load models for the provider — three steps from the actual cause. hive substitutes the agent's workspace, but only when the container really lacks the path, so a spec that bind-mounts a host directory at the same path still resolves to itself. The workspace is HIVE_ACP_WORKSPACE, else /home/agent/work, else the image's WORKDIR, and it is resolved lazily — two docker calls that a session with a usable cwd never pays for. cwd and MCP injection are now independent reasons to rewrite: gating the cwd fix on having MCP servers would have left the plainest agent, with no MCP at all, as the only one unable to open a session. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Gw5rgVt1CQ8EYrLpZtQjdM
…ntries The image carries every harness in the catalog, so a container can run any of them. Pinning the choice to the spec meant one Buzz harness entry per agent; an override means `hive (claude)`, `hive (codex)` and so on can all be the same hive-acp, and Buzz's harness picker stays the harness picker. Model selection needs nothing here. A custom harness definition carries no model env var, and selecting Opus reaches the harness anyway — Buzz drives it over ACP's own config options. So each entry reports the models its harness really supports, and hive never maintains a merged catalog that would rot on every upstream release. What the override does NOT move is credentials: hived provisions the container from the spec, so only the spec harness's credential is present. Overriding to a harness the container cannot authenticate stays allowed — refusing would make it impossible to start a container in order to log in — but it now says so at startup, naming the harness, the missing env vars and the two ways out. Verified: overriding uni to codex prints that warning and then fails with exactly the predicted `Authentication required`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Gw5rgVt1CQ8EYrLpZtQjdM
Subscription auth is how these tools are normally used, and for codex that is auth.json on disk rather than a key in the environment — an API key is the fallback, not the norm. The catalog could only describe the env form, so anything reasoning about "is this harness authenticated" was blind to the common case. Caught by its own false alarm: overriding uni to codex warned that no CODEX_API_KEY / OPENAI_API_KEY was set, and codex then authenticated from auth.json and listed GPT-5.6. A warning that fires on a working container is worse than no warning, because it teaches the reader to ignore the line that will one day be true. `credential_file` records where each harness reads that file, absolute and under the state volume — a credential written anywhere else is destroyed on the next recreate, after which the agent silently reverts to unauthenticated. hive-acp now checks both forms before warning; verified both ways, with codex silent and goose still reporting. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Gw5rgVt1CQ8EYrLpZtQjdM
One key per harness id meant every agent on a box shared one subscription, which is wrong in both directions. An agent running on someone else's tokens had no way to say so, and a second subscription — the obvious move when the first one runs out — could not be expressed at all. Swapping a credential meant overwriting the one every other agent was using. `[harness] credential` names the broker key, defaulting to `harness/<id>` so the common case stays untyped. Rotating is then `hive secret put` against a different key, and which agent uses which is a spec line rather than a global. Auth mode still wins: naming a key does not resurrect the demand that `auth = "interactive"` removes, since a harness logged in inside the container holds its credential in the state volume whatever the key is called. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Gw5rgVt1CQ8EYrLpZtQjdM
HIVE_AGENT named the wrong thing, and the name hid a real bug. It picks a container — image, state volume, network, credentials, MCP servers — and says nothing about who the agent is: in environment mode the container has no Nostr key at all, since buzz-acp holds it on the host and Buzz strips it from the harness environment before spawning. Meanwhile "agent" is exactly what Buzz calls the entity that *does* have the identity. So several Buzz agents pointing at one HIVE_AGENT were silently one container sharing sessions, skills and credentials, and the variable read as though it were per-agent. HIVE_AGENT still works, with a warning rather than a break. It is written into harness definitions that already exist, and a rename that takes a running agent offline to make a point is not an improvement. Also corrects the module header: the provider seam is not "hive competing with Buzz over location" but a different job entirely. The deploy payload carries agent_command and env_vars verbatim, so a provider that starts buzz-acp on another host spawns hive-acp there with HIVE_ENV intact — the two seams compose rather than conflict, which is the whole argument for sitting in the harness one. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Gw5rgVt1CQ8EYrLpZtQjdM
grok uses GROK_HOME for BOTH its install and its state, and it writes there:
session storage, settings, and auth.json — which it reads from GROK_HOME
rather than from ~/.grok. Pinning it read-only at /opt/grok, to stop every
agent re-bootstrapping its own 127 MB binary, made grok unusable:
session search bootstrap failed: unable to open database file:
/opt/grok/sessions/session_search.sqlite
No credentials found: no login token and no model api_key/env_key
→ session/new fails FS_PERMISSION_DENIED (os error 13)
The credential half is the trap. A correct `grok login` writing
~/.grok/auth.json is invisible when GROK_HOME is set, and the error that
surfaces names a filesystem problem — so the obvious next move is to chase
permissions rather than the credential path. Delivering auth.json as a hive
file credential to ~/.grok looked right and changed nothing.
Now the install stays shared at /opt/grok and GROK_HOME points into the
per-agent state volume, with the binary symlinked in by the entrypoint:
writable state per agent, one 127 MB copy on disk. The install-time ENV is
kept so the postinstall still bootstraps into /opt/grok, plus a `test -x`
so a future npm change that skips it fails the build rather than shipping
an image whose grok is missing.
Verified against the running container: with GROK_HOME writable, session/new
returns a sessionId instead of FS_PERMISSION_DENIED.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gw5rgVt1CQ8EYrLpZtQjdM
/home/agent/work is where an agent actually writes — hive-acp redirects the client's cwd there. It was a plain directory baked into the image, so it sat on the container's ephemeral layer and every recreate deleted it, while the agent's sessions and credentials survived on the volume next to it. A spec edit is enough to trigger a recreate. An empty state/work already existed on the volume, unused: someone knew it belonged there and wired up the other one. Now linked into the volume by the entrypoint, and NOT created in the image — `link_state` refuses to replace a real directory, so leaving the mkdir in would have silently defeated the fix. smoke.sh gains a persistence section asserting every path an agent writes to resolves onto the state volume. Two were wrong at once — this and GROK_HOME — and both read fine in the Dockerfile; the failure only shows up one recreate later, with no error anywhere. Checking "does the harness answer ACP" was never going to catch it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Gw5rgVt1CQ8EYrLpZtQjdM
A non-interactive `ssh host 'hive ...'` — exactly how buzz-backend-hive deploys — gets PATH=/usr/bin:/bin:/usr/sbin:/sbin, with neither Homebrew nor /usr/local/bin on it. Bare `docker` then failed from inside the wrapper with /Users/uni/.local/bin/hive: line 47: exec: docker: not found which reads as hive being broken rather than as a login-shell difference, and only ever appears on the deploy path — never when a human runs the same command in a terminal. Same candidate list as hive-acp's find_docker and DockerBackend::discover, with HIVE_DOCKER as an override and an error naming where it looked. Found while checking that a laptop can actually deploy to this box: `hive` itself was also missing from that PATH, which is a host setup problem rather than a hive one — ~/.zshenv, since `ssh host cmd` sources it and nothing else. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Gw5rgVt1CQ8EYrLpZtQjdM
Two blockers, both only visible by running the real Buzz 0.5.0 payload
through the provider rather than reading the code.
1. The spec was installed with `sh -c 'mkdir -p DIR && cat > DIR/x.toml'`,
which assumes the spec directory is a path on the machine ssh lands on.
Where hived runs in a container — which on macOS and Windows it must —
/etc/hive/agents is a VOLUME, and the redirect fails:
mkdir: /etc/hive: Permission denied
The asymmetry was the clue: `hive secret put` in the same deploy
SUCCEEDED, because it goes through the CLI, which knows where the daemon
lives. So the spec goes through the CLI too. New `hive spec-put <name>`
reads a spec on stdin, validates it, and installs it write-then-rename —
hived watches that directory, and a half-written file is a spec it will
parse and reject. One code path now serves a native daemon, a
containerized one, and a remote one.
2. Buzz's deploy payload carries `private_key_nsec` but NOT the pubkey, so
the provider wrote `pubkey = ""`. That is not cosmetic: hived uses
identity.pubkey to detect two specs deploying one identity to the same
relay — which answers every mention twice and charges the owner twice —
so an empty one makes every provider-deployed agent collide with every
other, and hived holds all of them.
Derived from the nsec now. bech32 is decoded here rather than added as a
dependency; the checksum is the part that matters, since a mistyped key
must fail loudly rather than derive a plausible wrong identity.
Upstream would be better for (2): the desktop has record.pubkey and could
send it, and no provider should need secp256k1 to learn which agent it is
deploying.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gw5rgVt1CQ8EYrLpZtQjdM
…arly
Every remote failure surfaced to the desktop as a bare "installing the agent
spec". The reason — validation, a missing binary, an ssh refusal — sat one
level down in the anyhow chain, discarded by `e.to_string()`, which prints
only the outermost context. `{:#}` prints the chain. The desktop shows this
string verbatim and there is nowhere else to look, so this was the
difference between a diagnosable failure and a shrug.
With it visible, the first real deploy then reached the container and
crash-looped:
idle_timeout (900s) must be less than max_turn_duration (600s)
buzz-acp enforces that pair; hive-spec did not, so `hive validate` passed,
hived reconciled, and the reason was only in `docker logs`. Validated now,
so it fails at deploy with the reason attached.
Verified end to end afterwards: a deploy over ssh writes the spec, hived
reconciles it, the container is created and buzz-acp starts.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gw5rgVt1CQ8EYrLpZtQjdM
buzz-backend-hive occupied Buzz's provider seam, which answers "where does this agent run", and then did hive's job there — resolving harnesses, writing specs, storing credentials. So selecting hive as the harness AND hive as the provider produced nonsense, and the two dropdowns used one word for opposite things. That asymmetry was ours, not Buzz's. A provider whose only job is location composes with harness=hive exactly as it composes with harness=claude, because the deploy payload carries agent_command and env_vars verbatim. The replacement is a generic host provider, which hive should not be shipping. Deleted. What makes the harness seam sufficient on its own: * identity is now OPTIONAL on a spec, required only in mode = "relay". An environment has none by design — buzz-acp holds the key outside the container — so every generated spec was carrying a pubkey and relay url nothing reads. hived skips identity-less specs in duplicate detection; treating a missing identity as a shared empty key would have made every environment a duplicate of every other and held all of them. * hive-acp CREATES an environment when HIVE_ENV names one that does not exist, so an agent needs no hand-written TOML on the host. The generated spec is a harness id and a mode, because there is nothing else to invent: identity belongs elsewhere and credentials are named, not stored, here. * HIVE_ENV is no longer pinned in the harness definition. That pin was the reason to point every agent at one spec, which silently put them in ONE container sharing sessions, skills and credentials. Set per-agent, each gets its own container and its own state volume. * A held agent now says why. hived holds an agent whose credentials are missing and logs the reason where nobody is looking, so the caller waited out its patience and reported a timeout naming nothing. hive-acp compares requirements against the broker and prints the key to store. Verified from a clean slate: HIVE_ENV=research with no spec, no container and no volume creates all three, reports its models, and lands on its own hive-research-state rather than sharing uni's. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Gw5rgVt1CQ8EYrLpZtQjdM
A generated environment defaulted to broker+env, which is right for claude — a token in CLAUDE_CODE_OAUTH_TOKEN — and wrong for codex and grok, whose normal auth is a JSON file. Selecting "Codex (hive)" therefore produced a spec demanding `harness/codex` while what the box held was `codex/auth`, so hived held the agent for a credential nobody was going to create and it simply never started. hive-acp now asks the broker what exists and, when the catalog says the harness reads a file, emits `auth = "file"` plus the [[file]] block pointing at it. Both key spellings are tried: `harness/<id>` is what hive asks for by default, `<id>/auth` is what a file credential gets called when stored by hand. Also publishes BUZZ_HOST_UNIT as a fallback for HIVE_ENV, so a deployed agent needs no environment variable set by hand before it can run. Asking for one invited the mistake it was meant to prevent: reusing a neighbour's value and silently sharing that container's sessions and credentials. Verified from a MacBook against uni, each harness auto-provisioning its own environment and reporting its own models: codex gpt-5.6-sol[low..ultra], gpt-5.6-terra, gpt-5.6-luna, gpt-5.5, gpt-5.4 grok grok-4.5 claude default, sonnet, opus, haiku Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Gw5rgVt1CQ8EYrLpZtQjdM
… name Dropping the HIVE_ENV pin from the harness definition fixed isolation and broke the model list. In the create-agent dialog the desktop probes the harness for its models — and at that moment nothing has named the agent, because the agent does not exist yet. hive-acp refused with "HIVE_ENV is not set" and the dropdown stayed empty, with nothing the user could set to fix it: pinning a value in the definition is precisely what put every agent in one shared container. So discovery falls back to a scratch environment. One PER HARNESS, not one shared: `session/new` needs that harness's own credentials before it will answer, so a single probe container would report claude's models for every entry, or fail outright for the ones it cannot authenticate. Resolution order is now explicit → supervisor → discovery scratch: HIVE_ENV set by hand, always wins BUZZ_HOST_UNIT per-agent, published by the supervisor probe-<harness> shared, and named so it reads as scratch Verified from a MacBook with nothing but HIVE_HARNESS set, the way the desktop calls it: claude default, sonnet, opus, haiku codex gpt-5.6-sol[low..ultra] and 24 more grok grok-4.5 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Gw5rgVt1CQ8EYrLpZtQjdM
Deployed as claude, codex and grok, the first two worked and grok never answered. It was delivered its credential correctly — 1733 bytes at 0600, present immediately after create — and had deleted it by the time it finished starting, leaving only auth.json.lock. The stored token was 24 minutes past its expires_at, and grok's refresh half rotates on use: whichever grok refreshes first invalidates every other copy, so the container's grok could not renew a token the host's grok had already rolled. It gave up and removed the file. Injecting a snapshot is therefore worse than injecting nothing. The agent ends up unauthenticated while its spec records that a credential was delivered, and the only symptom is an agent that never replies. `credential_file_rotates` marks a harness that owns and rewrites its own credential. Auto-provisioning gives those `auth = "interactive"` and prints the one command that fixes it, rather than `auth = "broker"` — which would hold the agent forever waiting for a key that by design lives in the state volume, and hold it so hard the container needed to log in never starts. codex is deliberately not marked: its auth.json records a last_refresh rather than a hard expiry, and it is still working in a container hours after being injected. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Gw5rgVt1CQ8EYrLpZtQjdM
A generated environment had no MCP servers at all. Every agent created from the desktop therefore came up unable to reach the vault, while the one environment somebody had configured by hand could — so the feature appeared to work and silently did not for anything new. Three real agents were created before anyone noticed. /var/lib/hive/defaults.toml is appended verbatim to every environment hive-acp creates, and validated by `spec-put`, so a broken defaults file fails at creation with a reason rather than producing an agent that quietly lacks its tools. Deliberately NOT in the spec directory: hived reads every *.toml there as an agent, so a defaults file next to the specs would be reconciled as one. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Gw5rgVt1CQ8EYrLpZtQjdM
The hived runtime is debian:bookworm-slim with a docker binary copied in and nothing else — no curl, no wget, no openssl, no python. The OAuth flow shells out to curl, so the subcommand died at its first request with Error: probing https://…/mcp Caused by: No such file or directory (os error 2) which names a file and does not name curl. Written and tested against a host build, where curl is simply always present; it had never once been run where it actually ships. curl and ca-certificates are installed now, with `curl --version` in the same layer so a base image that stops shipping it fails the build rather than the feature. Also: omitting --scope now sends NO scope parameter rather than requesting everything the resource advertises. The consent screen is what should decide — it knows which scopes exist, which this user may have, and how to ask. `scopes_supported` describes what the RESOURCE understands, not what the server will grant, so naming them from it silently caps the token at the published set and cannot obtain anything outside it. Which is exactly the case here: the metadata lists vault:read/write/admin and nothing about account scopes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Gw5rgVt1CQ8EYrLpZtQjdM
The image had git, curl, jq, node, npm and tar. No ripgrep, no python, no compiler, no ssh, no less, no unzip, no sqlite. An agent in a container cannot apt-get its way out of that: it has no root, and anything it did install would be destroyed on the next recreate. So the image has to anticipate, and not anticipating fails quietly — a harness that shells out to `rg` falling back to something slower, a native npm module that will not build, `git log` with no pager. Added: ripgrep, fd, python3 + pip + venv, build-essential, pkg-config, openssh-client, rsync, less, vim, nano, tree, file, unzip, zip, sqlite3, and gh from GitHub's own release since Debian does not package it. Deliberately NOT added: Go, Rust, and other language toolchains. They are large, and an agent that needs one wants a specific version rather than whatever Debian happens to ship. Each tool is asserted present in the same layer that installs it, so a base image that stops shipping one fails the build rather than producing agents that quietly cannot search. smoke.sh passes 9/9 ACP and 9/9 persistence. Also reverts the scope default. Letting the consent screen decide is right in principle, but a server that receives no scope may grant nothing — and Parachute does exactly that, offering no choices and returning a useless token. Advertised scopes by default; `--scope ""` for a server whose consent screen should own it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Gw5rgVt1CQ8EYrLpZtQjdM
Typing "uni" into the HIVE_ENV field left behind environments named `u` and `un` — each with a spec, a container and a volume, all created within the same second. The desktop re-probes the harness on every keystroke, and provisioning fired on every probe. Cheap to create is exactly what made it bad: nothing failed, nothing warned, and the clutter only surfaced when someone listed containers hours later. A deployed agent carries BUZZ_HOST_UNIT — its supervisor named it. A probe does not. So a probe naming an environment that does not exist now falls back to the shared discovery environment and says so, rather than conjuring one; creation is reserved for something that has actually been deployed. Also collapses the spec read into `read_spec`, which was three near-copies of the same local-then-daemon lookup. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Gw5rgVt1CQ8EYrLpZtQjdM
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Follow-up to #4. Two of the commits below were pushed to that branch after it had already been merged, so they never landed — this reopens them on top of current
main, with a third fixing the bug that showed up the moment Buzz drove it for real.The bug
Creating an agent in Buzz with
harness=hivefailed at the model picker: "could not load live models for this provider", and no agent could be created.buzz-acp modelsspawns the harness, sendsinitialize, thensession/new, and reads the models out of the response. Thesession/newit sends carries thecwdof the machine Buzz is on. The harness is inside a container, where that path does not exist —claude-agent-acpvalidates it and fails the session:No session, so no models, so no agent. Three steps from the cause, and nothing in the message points at a container boundary.
The fix
hive-acpsubstitutes the agent's workspace — but only when the container genuinely lacks the path, so a spec that bind-mounts a host directory at the same path still resolves to itself. The workspace isHIVE_ACP_WORKSPACE, else/home/agent/work(which the agent image creates for exactly this), else the image'sWORKDIR. Resolving it costs twodockercalls, so it is lazy — a session with a usablecwdnever pays for it.cwdand MCP injection are independent reasons to rewrite. Gating thecwdfix on having MCP servers would have left the plainest possible agent — no MCP at all — as the only one unable to open a session.Verified
Against the live box, through the real
buzz-acpbinary from Buzz 0.5.0:136 workspace tests pass; 12 in
hive-acp, including that the workspace is not resolved when thecwdis already good.🤖 Generated with Claude Code
https://claude.ai/code/session_01Gw5rgVt1CQ8EYrLpZtQjdM