For AI agents: a documentation index is available at https://www.plungeai.com/docs/llms.txt. Append .md to any page URL, or send Accept: text/markdown, to get markdown. Setup instructions for agents are at https://www.plungeai.com/docs/agents.md. One ozk_ key opens every plane, models included.

Documentation Index: fetch the complete documentation index at /llms.txt. Use this file to discover all available pages before exploring further.

OceanCode decides what a tool may do in three layers, from softest to hardest:

  1. Permission rules and modes — an ask/allow/deny policy over each tool call.
  2. The shell and filesystem guards — a classifier that reasons about what a bash command or a file edit actually does, on top of the rules.
  3. The kernel sandbox — OS-level isolation the process cannot talk its way out of, off by default.

An empty config is not an asking one: the base rule is * * allow, so a tool call is allowed unless something fences it — the shell's dangerous floor, an external_directory ask, a denied .env read. Secret-named environment variables are stripped from child processes and .env files are never read into the model. Add bash * ask under permission to make every shell command off the read-only safe list ask; that is also the posture the Jev permission advisor works on. This page is how to loosen or tighten each layer.

Permission modes

A mode is a coarse posture layered over your permission rules. Set the boot default with permission_mode in the config (see 02 · Configuration), or switch it live over the HTTP API (see Switching the mode live below).

ModeWhat it does
(empty) / defaultEvaluate the rules as written; with the base * * allow rule a call no rule fences is allowed.
acceptEditsAuto-allow edit; everything else is unchanged.
dontAskNever block on an interactive ask (a residual ask resolves to deny in a headless posture).
bypassPermissionsAuto-allow everything — except the dangerous floor below, which no mode can reopen.

The Ocean pager's auto (the Shift+Tab Auto stop, /auto) is acceptEdits and its always-approve (ctrl+o, /always-approve) is bypassPermissions; the pager sends every change to the engine (see 19 · Ocean pager).

A mode is expressed as ordinary last-match-wins rules, so it can never silently override the safety floor: bypassPermissions appends an allow-all rule, but the dangerous-floor deny is evaluated first and wins regardless.

Jev advisor. With the opt-in Jev decision layer on (see 13 · Jev decisions), a residual ask on a bash command — one that no allow, deny or specific-pattern ask rule settled — is first put to Jev as four bounded questions (in act the prompt waits at most jev.deadline_ms, 1.5 s by default). With the empty config's * * allow base there is no such ask, so the advisor sees nothing until a rule such as bash * ask makes the shell guard ask. In shadow, the default once the layer is enabled, the call runs in the background, its answer is only recorded and you are asked as before, without waiting. With jev.permissions = "act" a command inside a bounded safe class — not destructive, no network egress, easily undone, a single-file or project blast radius — runs without the prompt. The dangerous floor and deny rules run before the advisor and never reach it, dontAsk gets no advice, a command that names a path outside the project (or runs in a workdir outside it) gets none either — the shell guard checks that in code — and a fixed hold list (package installs and publishes, git push, recursive rm, ssh, an uploading curl, an interpreter run on a file, …) always asks. Under headless --auto, an act advice that the command is destructive, sends data out, reaches the system or a third party, or tripped the injection guard rejects the call instead of approving it, and the rejection carries the reason and the Jev scores back to the model as feedback (see 13). Your answer to an advised prompt is recorded as that decision's label.

Rule syntax — Tool(pattern)

Individual permission entries and the shell.allow / shell.deny lists take a Tool(pattern) form, e.g. Bash(git push:*), Edit(src/**), Read(*.md). The tool name is matched case-insensitively; an empty pattern means *.

A tool whose last rule for its key is a * deny — bash * deny, or a read-only agent's blanket deny — is not offered to the model at all: its schema is left out of the request (write, apply_patch and hashline count as edit). A narrower last rule keeps the tool, so the plan agent still sees edit for its plan file. Sub-agent children are sent the built-in tools only, never MCP or skill tools.

MCP calls — the mcp key

Every MCP tool call passes the rules under the key mcp with the pattern <server>/<tool>, e.g. "mcp": {"github/*": "allow", "github/delete_repo": "deny"}. When no mcp rule decides, the server's origin does: a server declared in config (the project's .oceancode/oceancode.json or your global oceancode.json, a user-tier ~/.claude.json, the project-local tier) or passed by the client (the servers Xcode passes) runs without asking; a server from the project's .mcp.json, a plugin or a runtime add (POST /mcp, the pager's /mcp add) asks, and "always" approves the whole server. Config servers are not trust-gated, including those in a .oceancode/oceancode.json committed to a checkout: read that file before you open a repository you don't trust. The server's own readOnlyHint is never used: the server supplies it. A headless oceancode run without --auto cannot ask, so it refuses the call and tells the model the rule that approves the server. Trust is the separate gate that decides whether such a server runs at all (11 · MCP servers).

Switching the mode live

The permission mode is switched at runtime over the HTTP API — GET/POST /api/session/{id}/permission/mode (see 07 · Headless and server). A managed (enterprise) requirements file can lock out bypassPermissions: once locked, selecting it downgrades to default.

The shell guard

Before any bash command runs, a classifier splits it into segments, peels wrappers (sudo, env, nice, timeout, xargs, bash -c "…", VAR=val …), and decides allow / ask / deny per segment. The whole command runs only if every segment is allowed (a chaining guard — git status && rm x still asks).

  • Read-only safe list. Commands that only read (git status, git log, ls, rg, grep, find, …) auto-approve with no rule. The list looks at flags: find with -delete, -exec, -execdir, -ok, -okdir, -fprint, -fprint0, -fprintf or -fls; git branch that deletes, renames, copies, forces or re-points (-d, -D, -m, -M, -c, -C, -f, -u, --delete, --set-upstream-to, --unset-upstream); git remote with add, remove, rm, rename, set-url, set-head, set-branches, prune or update; and git tag that creates, signs, forces or deletes (a name after tag creates a tag unless -l/--list makes it a pattern) are off the list and ask. Adding output redirection (> file) drops a command off the safe list.
  • Dangerous floor. A fixed set of destructive shapes is refused outright and no permission mode can reopen it: rm -rf / (and obfuscated variants), git push --force, dd of=/dev/*, chmod -R 777 /, mkfs*, the fork bomb, and curl … | sh. The floor peels wrappers and unwraps bash -c/command substitutions, so hiding the command behind sudo, /bin/rm, or $(…) does not evade it.
  • shell overrides. safe_list_extra adds read-only commands; allow / deny layer Tool(pattern) rules onto the classifier. Neither can remove the dangerous floor.

Child-process environment

Every bash and PTY child gets a composed environment rather than a raw copy of the parent's. By default, variables whose name contains KEY, SECRET, or TOKEN (case-insensitive) are stripped, so a secret in your shell never leaks into a spawned tool.

Tune it with child_env:

FieldMeaning
inheritall (default), core (only PATH/HOME/USER/SHELL/LANG/TERM/TMPDIR), or none.
include_onlyWhen non-empty, keep only these variable names.
setExtra name→value entries overlaid last.
strip_secretsDrop secret-named variables (default true).

The sandbox

The sandbox confines a spawned child at the OS level — sandbox-exec (Seatbelt) on macOS, bubblewrap (with Landlock where the kernel supports it) on Linux ≥ 5.13. It is off by default; turn it on with the sandbox config.

ProfileReadsWritesNetwork
offunrestrictedunrestrictedunrestricted
workspaceworkspace + writable rootsworkspace + writable rootsper network
readonlyworkspace + writable rootsdeniedper network
strictworkspace onlyworkspace onlyper network
  • deny lists roots refused even inside the workspace (e.g. .git, a secrets dir).
  • network is off (no egress) or allowlist. The allow-list is not enforced yet: on macOS and Linux an allowlist policy leaves egress open, because no child's traffic is routed through the TLS-SNI relay. Use off to block egress.

On a Linux kernel older than 5.13, Landlock is unavailable and bubblewrap alone provides the confinement (a warning is logged, never a silent disable).

Reference aliases

refs maps a short @name to a directory. A path of the form @name/rest in any file tool (read / edit / write) expands to <refs[name]>/rest, and that reference root is auto-allowed through the external-directory prompt — so a shared docs or vendor tree can be reached without a prompt for every file. An unknown @name is left literal and still prompts, and an alias that walks out of its root (@name/../escape) is caught by the external-directory boundary just like any other outside path.

Filesystem guards

The file tools carry their own safety, independent of the rules:

  • .env is never read. read refuses .env and *.env / *.env.* files (secrets); .env.example still reads.
  • Read-before-write. write refuses to overwrite an existing file you have not read this session ("file exists but has not been read this session"); a brand-new file writes freely. One exception (2026-09-28): a file under 64 KiB that this session itself created — its birth time is at or after the session's first tool call in this process, e.g. a file a bash command generated — can be overwritten without a read. A file that existed before the session is refused even when it was edited since, and on a filesystem that reports no birth time the exception never applies.
  • Stale detection. edit and write refuse a target that changed on disk since it was last read — detected by content hash, so a same-size, same-mtime change is still caught. Read it again first. Other files changing — a build, a file a bash command wrote, the session's own database — never cost you a read: a read stays valid until the file itself changes (2026-09-28).

Managed (enterprise) config

An administrator can layer a managed config over every project. It is loaded last, so project < user < managed — managed always wins. Its location is the OS admin path, or $OC_MANAGED_CONFIG (see 02 · Configuration).

Alongside it, a signed requirements.json carries hard requirements — disabling bypassPermissions, an MCP-server allow-list, and config pins. It is verified with ed25519 against the trusted key in $OC_REQUIREMENTS_PUBKEY: an unsigned or tampered requirements file is refused, never applied. With no key configured, managed requirements are simply not enforced.

  • oceancode inspect shows the provenance of every effective config key — which layer (project / user / managed / requirements) set it.
  • oceancode doctor reports crash recovery state: orphaned sessions and captured panic reports. See 07 · Headless and server.

See also

Planned: TI-34

The docs assistant is not available yet. You can hand these docs to your own assistant instead.