# Agents on Shark

How to point a coding agent or your own agent loop at Shark. Pick by where the agent runs:

| your agent | path | changes to the agent | status |
|---|---|---|---|
| Claude Code, Codex, Cursor or any agent with a shell, on your machine or server | **A. `shark run` as its command runner** | one instructions snippet | GA-path |
| your own loop (any model, any framework) | **B. one tool backed by the SDK** | one tool definition | GA-path |
| Claude Code, unmodified, running next to the Space | **C. the shell shim `shark-bash`** (+ `shark-rg`) | none: environment variables | beta, self-hosted |
| a harness that cannot be intercepted | **D. `sharkfn` over MCP** | one MCP server | beta |
| Claude Code, Codex or OpenCode, running **in the cloud** with its UI in your terminal | **[`shark code`](shark-code.md)** | none | beta, validated locally only |
| LangChain, LangGraph, smolagents, Prefect | the SDK's integrations | a few lines | beta: [integrations.md](integrations.md) |

In every path the model keeps thinking where it already does; only commands run in Shark, each in a fresh microVM next
to the agent's persistent files and services. No VM exists for the Space while the model thinks.

## A. Claude Code, Codex, Cursor: `shark run` as the command runner

1. Install the CLI and log in once on the machine where the agent runs (`shark login --api-key -`;
   [quickstart.md](quickstart.md)).
2. Create a Space for the project: `shark create --name myproject`, and put the code in it:
   `shark cp -r . myproject:.` (or `git clone` inside it).
3. Paste the [AGENTS.md snippet](agents-md.md) into the project's instructions file: `AGENTS.md` (Codex, Cursor and
   others) or `CLAUDE.md` (Claude Code). It tells the agent to run builds, tests and installs with
   `shark run myproject -- '…'`, how exit codes work, and what not to do with keys.

What this gives you: the agent's heavy and risky commands run in a microVM with the project's files, its own Postgres
or Redis when declared, and the Space's egress policy; your machine runs only the agent. Files the agent edits
locally are not synced into the Space by this path: have it write files through commands (`git apply`, heredocs),
push changes with `shark cp`, or keep the whole project in the Space.

Codex runs its shell commands through its own sandbox; `shark run` needs outbound HTTPS to `hel1.sharkmagic.si` from
there (allow the host in the agent's network settings if it restricts network).

## B. Your own agent loop: one tool, the SDK, `op_id` = tool call id

Expose one tool to the model and back it with `space.run`. Pass the tool call's id as `op_id`: if your loop crashes and
replays a tool call, or retries a request, Shark returns the stored result instead of running the command twice.

Tool definition (JSON Schema; adapt the envelope to your model provider's tool format):

```json
{
  "name": "run",
  "description": "Run a bash command in the project's persistent Shark Space. Each command runs in a fresh Linux microVM; files persist between commands, shell state (cd, export) does not. Returns exit code, stdout and stderr.",
  "input_schema": {
    "type": "object",
    "properties": {
      "cmd": {"type": "string", "description": "bash command line, run from the Space root"},
      "cwd": {"type": "string", "description": "optional directory relative to the Space root"},
      "timeout_s": {"type": "number", "description": "optional; up to 900"}
    },
    "required": ["cmd"]
  }
}
```

Handler (Python):

```python
from shark import Shark

shark = Shark()                                    # SHARK_API_KEY (an account key) from the environment
space = shark.spaces.get("myproject")

def run_tool(tool_call_id: str, cmd: str, cwd: str | None = None, timeout_s: float = 120) -> str:
    r = space.run(cmd, cwd=cwd, timeout=timeout_s, op_id=tool_call_id)
    out = f"exit: {r.exit}\n"
    if r.shark_status != "ok":
        out += f"shark_status: {r.shark_status}\n"
    return out + f"stdout:\n{r.stdout[-20000:]}\nstderr:\n{r.stderr[-10000:]}"
```

TypeScript is the same: `await space.run(cmd, { cwd, opId: toolCallId })`. Long commands: `space.stream(cmd)` yields
output as it arrives; `space.start(cmd)` returns an `Op` you can re-attach to from another process with
`space.op(op_id)`.

Patterns that come free with Shark:

- **One Space per agent, user or customer**, each with its own files and services; idle ones cost no compute.
- **Fork per attempt.** `space.fork(count=3)` and run three fixes in parallel; keep the Space whose tests pass, delete the
  rest.
- **Fork per eval or test shard.** One template Space, `fork(count=50)`, one shard per fork.
- **Read files without a VM.** `space.files.read_text(path)` and `space.files.list(...)` read the committed tree directly;
  a "read file" tool costs no lease.

## C. Unmodified Claude Code: the shell shim

`shark-bash`, self-hosted, beta.

How the metal proofs ran real Claude Code with no change to the harness or its prompt. Claude Code runs on the Shark
host; its file tools (Read, Edit, Write) work on the Space's host mount; every Bash command goes to a microVM.

1. Install `shark-bash` as a file named `bash` (Claude Code only accepts a shell whose path contains `bash` or `zsh`)
   and point the harness at it: `CLAUDE_CODE_SHELL=/opt/shark/bin/bash` (or `SHELL`).
2. Start Claude Code with its working directory inside the Space and the Space's host environment (`SHARK_W`,
   `SHARK_SP`, `SHARK_LEASE`, `SHARK_CTL`), which the host writes when it provisions the Space.
3. By default the shim runs pure reads (`cat`, `ls`, `grep`, `git status`, …) on the host mount and everything else in a
   microVM. `SHARK_STRICT=1` sends everything to a microVM and refuses (exit 126) what cannot go there; strict mode in
   the shim is opt-in because its own pre-registered test did not pass (P76). The HTTP API is always strict.

Per command the shim snapshots the harness's edits into the Space, runs the command in a lease (output streamed while
it runs), brings the lease's changes back into the harness's view and reports the final cwd. Ctrl-C and timeouts kill
the VM. A second command while one runs waits `SHARK_BUSY_MS` (3,000) and then exits 125, unless
`SHARK_CONCURRENCY=N|auto`.

Measured on the AX41 (2026-11-05), for commands run through Shark: **91–96 % of a command's time is the user's
command**; the rest is Shark's overhead (lease start, mount, commit). A lease itself costs **44 ms**.

Status: used in the metal proofs with Claude Code 2.1.303 and 2.1.316. Provisioning a Space on a host is part of the
proof kit, not a packaged installer: this path is for self-hosted nodes and design partners, not the hosted node.

### Grep and Glob: shark-rg

Beta.

Claude Code's Grep and Glob call `rg`. Over a lazily-loaded FUSE mount, walking a large tree is slow. `shark-rg` is an
`rg`-compatible binary that answers those calls from an index of the Space's last commit plus what changed since, and
runs the real `rg` with the same argv for anything it does not cover. The tool definitions the model sees are unchanged
(byte-identical, so no token cost).

```sh
export USE_BUILTIN_RIPGREP=0                    # make Claude Code use the rg on PATH
export PATH=/opt/shark/rg-shim:$PATH            # a dir where shark-rg is installed as "rg"
export SHARK_RG_MOUNTS=/path/to/space=/path/to/W/ctl.sock   # mountpoint=control socket[,...]
```

`SHARK_RG_OFF=1` forces the real `rg`. Measured on the AX41 (2026-11-05): served from the index (`sharkfn`), Grep and
Glob on a 100k-file tree are **300–600× faster than ripgrep over the FUSE mount**. Flag combinations that gave a
different answer in testing go to the real `rg` by default (P95).

## D. MCP: sharkfn

Beta.

`sharkfn` serves Read, Write, Edit, Glob and Grep from a Space's committed tree, with no VM:

```sh
sharkfn serve -repo R -mount /workspace -stdio -space <id>      # one Space over stdio
sharkfn serve -repo R -mount /workspace -http 127.0.0.1:8792    # many Spaces: POST /mcp/<space>
```

Use it when the harness cannot be intercepted. MCP adds tool definitions to every model request, so it is not the
default path; for Claude Code prefer `shark-rg` (no extra tokens). Self-hosted nodes only today.

## Keys and agents

- Give the agent a key with only the scopes it needs. For path A, a default key (`spaces:read/write`, `exec`,
  `files:read/write`, `usage:read`) is enough; `secrets:write` and `network:write` belong to you, not the agent.
- Never put the key in the prompt, the repo or the Space. The CLI reads `~/.shark/credentials`; the SDKs read
  `SHARK_API_KEY`.
- Third-party credentials the agent's commands need (GitHub, a data API): store them as Space secrets with
  `expose: inject` ([concepts.md](concepts.md#secrets-inject-default-vs-exec)). The command sees a placeholder; the value
  never enters the VM, so the agent cannot print or exfiltrate it.
- To hand a single Space to an agent, give it a Space-scoped key: `shark keys create --name agent-myproject --space
  myproject` (the secret is printed once; put it in the agent's secret store, not in its prompt). Agents under
  `shark code` already get a credential limited to their Space.