Concepts

The trust boundary

Here is the question the whole architecture answers: the agent has to do real things — send mail, move money, write to a client's ledger — and you cannot trust it with the credentials that make those things possible. Not because it is malicious, but because anything it reads can instruct it, and everything it does is one bad inference away from being wrong in public.

The usual answer is to make the model better behaved. Mycel's answer is to move the capability out of its reach. The agent gets intent. The harness holds the capability. A human sits on the line between them.

  SANDBOX (untrusted)                │  HARNESS (trusted)
                                     │
  agent + skills + knowledge         │  connections, vault, policy
  opaque nonce ──── POST /v1/internal/actions/:capability
                                     │        │
                                     │   approval gate ── human approves / edits
                                     │        │
                                     │   real credential ──► the provider

What crosses the line, and what does not

Into the sandboxNever into the sandbox
Skills and knowledge for this wedgeConnection secrets — Stripe keys, mailbox tokens
The task input, and uploaded documentsMYCEL_DATABASE_URL, MYCEL_SECRET_KEY, DAYTONA_API_KEY, any observability keys
An action-grant nonce and a set of proxy URLsThe founder API key (msk_…)
A gate token for the OpenCode pluginAny other project's data

The local sandbox does not simply inherit the process environment — it is given HOME, PATH, LANG, TERM and a handful of equally boring variables. printenv inside the sandbox is deliberately dull.

The exception you have to know about. By default the model provider key — ANTHROPIC_API_KEY or equivalent — is passed into the sandbox, because OpenCode calls the model directly. Set MYCEL_PROXY_MODE=1 and model calls go through /v1/internal/llm/* instead: the sandbox holds a nonce, the harness holds the key, pins the model, and caps max_tokens at MYCEL_MAX_TOKENS. Connection secrets are unaffected either way — those never cross the line at all.

Three doors through the boundary

Everything the agent can do to the outside world goes through one of three endpoints, and they differ in exactly one dimension: how much damage they can do.

ReadsActionsTool gate
Endpoint/v1/internal/reads/:capability/v1/internal/actions/:capability/v1/internal/gate
Human gateNoAlways, unless policy matchesAlways, unless policy matches
Constrained byGET only, host fixed by the connection, 256KB cap, 200 per taskGranted connections for this task onlyTool name matching a pattern list
Reached byAgent, directlyAgent, directlyOpenCode plugin, automatically

Reads are ungated because reading is recoverable and because gating them would make every task unusable. They are still scoped: the agent supplies a relative path, never a host, so it cannot point a credentialled request at a server it chose. Absolute URLs, protocol-relative URLs, .. and header-injection attempts are all rejected.

The tool gate is the belt to the action proxy's braces. A plugin inside the sandbox intercepts tool calls whose name contains send, email, message, pay, charge, refund, delete, deploy, book or transfer, plus any action your wedge marked required. Matching is substring and case-insensitive, so it over-triggers rather than under-triggers — a tool called notebook_read will stop for approval. That is the correct direction to be wrong in.

If the gate cannot be reached at all, the plugin throws and tells the agent not to retry. It fails closed.

Which sandbox you are actually running

MYCEL_SANDBOXWhat it isUse for
localA temp directory and bash -lc on your host kernelDevelopment. Not a security boundary.
dockerA container per task, port bound to 127.0.0.1, removed on completionThe realistic default for self-hosting
daytonaRemote sandbox with auto-stopCloud, and the least exercised of the three

If MYCEL_SANDBOX is unset, the presence of DAYTONA_API_KEY selects daytona, otherwise local. A typo in the value falls through to local without complaint — so read the boot banner, which prints sandbox=…, rather than trusting your env file.

What the boundary does not give you

  • It is not a claim that the agent is safe. It is a claim that the blast radius of a bad decision is bounded by what a human approved.
  • task.tools is recorded, not enforced. Tool restriction is the gate plus a coarse bash denylist, not a fine-grained ACL.
  • The agent can still be wrong inside its permissions. A read it is entitled to make can return text that manipulates it. That output still has to clear the gate before it becomes an action — which is the point.

North star versus what ships

kernel/docs/ARCHITECTURE.md describes a larger system — durable mid-run resume, an MCP hub, task-scoped JWTs, deeper RBAC. That is the destination. Where those docs and server.ts disagree, the code wins. Build against routes that exist.

What to read next

Approvals — the human half of the boundary, and the only part of it your customers will ever notice.