sharkmagic.docs
shark code

Agent? Fetch sharkmagic.si/docs/llms.txt or this page as markdown

shark code: your coding agent in the cloud#

Status: beta, validated locally only. Every check below passed on our local rig with a fake terminal agent and a fake microVM (proofs/p116-shark-code, results r3). The real agents (versions below) were then run headless on a local machine with Shark's shell and a fake model: each one started with Shark's configuration, sent its shell command through Shark's shim and exited 0 (proofs/p116-shark-code/HARNESSES.md, results smoke-r1). The same checks on real microVMs are pending. Until then, treat this page as the design you will get, not a measured promise.

shark code runs Claude Code, Codex or OpenCode inside your Space, and shows its full terminal UI in your terminal: colors, resizing, Ctrl-C, everything. Close the laptop and the agent keeps working; attach again from any machine. No SSH, no keys to copy, no ports to open.

$ shark login
$ shark secrets set myproj ANTHROPIC_API_KEY --preset anthropic --allow-egress
Value for ANTHROPIC_API_KEY (input hidden):
set ANTHROPIC_API_KEY (inject → api.anthropic.com, header:x-api-key) on myproj
egress: profile anthropic-api on (shark egress show myproj)
egress: now allows api.anthropic.com
$ shark code --repo acme/api myproj
[shark: cloning acme/api into myproj]
shark: on branch shark/myproj of https://github.com/acme/api.git
[shark: agent on myproj (writer); Ctrl-\ detaches, the agent keeps running]
… the agent's UI …
^\
[shark: detached; the agent keeps running. shark attach myproj | shark logs myproj | shark stop myproj]

$ shark ls
tti-3f…   ready   myproj   2026-11-05T09:12:03Z   claude waiting-for-you 4m0s
$ shark attach myproj            # from any machine; shows the last 64 KiB first
$ shark attach myproj --view     # watch without typing (any number of viewers, one keyboard)
$ shark logs myproj              # what it printed since you last left
$ shark stop myproj
$ shark run-agent --agent codex myproj "fix the failing test"   # no terminal: streams output, exits with the agent's code

Without a Space name, shark code uses (or creates) a Space named after the repository (--repo), else after the current directory. Every verb takes --help with examples; stop, logs and ls take --json.

Choosing the agent: --agent, --remember, --forget#

The first time you run shark code on a terminal without --agent, it asks which agent to run and how it logs in, then offers to remember the answer. Nothing is saved before the last answer; q or Ctrl-C cancels.

$ shark code myproj
shark code: first run in myproj. Pick the coding agent (asked once if you remember it; scripts pass --agent).
Which coding agent?
  › 1) Claude Code  Anthropic's coding agent in the terminal (your Anthropic subscription login or an Anthropic API key)
    2) Codex  OpenAI's coding agent CLI (your OpenAI subscription login or an OpenAI API key)
    3) OpenCode  the open-source coding agent, any provider (its own login, or an Anthropic or OpenAI API key)
How does Claude Code log in?
  › 1) subscription login  log in inside the agent on its first start (your plan with the provider); …
    2) API key  a provider key (ANTHROPIC_API_KEY) stored as a Space or account secret; the VM only sees a placeholder
ANTHROPIC_API_KEY is not set for myproj. Add it now?          (API key only, and only when it is missing)
  › 1) add it to myproj  the guided secrets setup (preset anthropic); the value is asked hidden
    2) add it to the account  every devbox Space of the account gets it
    3) not now  shark code stops here; add it later with shark secrets
Remember Claude Code (api-key)?
  › 1) for this Space (myproj)  shark code myproj starts it without asking
    2) as the account's default  every devbox Space without its own choice
    3) no  ask again next time
[shark: created Space myproj (tti-…)]
[shark: remembered claude (api-key) for myproj; change: shark code --agent A --remember space, forget: shark code --forget space]
… the guided secrets setup (shark secrets setup), then the agent starts …

The key is never typed into shark code itself: the API-key choice runs the same guided setup as shark secrets (hidden value, preset for the provider's host).

Which agent runs, first match wins:

  1. --agent A (or $SHARK_AGENT)
  2. the Space's remembered agent
  3. the account's default (devbox Spaces only)
  4. on a terminal: the first-run questions above
  5. otherwise: exit 2, harness_required, with the command to run. Without a terminal, or with --json, nothing is ever asked.

--auth follows the same order: the flag, else the remembered login when the agent is the remembered one, else the node's default (api-key). --auth key is short for api-key.

shark code --agent list [--json]                                  # the agents this node runs, and how each logs in
shark code --agent codex --auth subscription --remember space myproj   # remember for this Space (then starts it)
shark code --agent claude --remember account myproj               # the account's default for every devbox Space
shark code --forget myproj                                        # forget the Space's choice (--forget account | all)
shark space set myproj --harness codex --harness-auth subscription     # the same, without starting anything
shark space defaults --harness claude                             # the account's default, without starting anything

The choice lives in the API, not on your laptop: the Space's in its settings (PATCH /v1/spaces/{id} {"harness": {"agent", "auth"}}, inherited by forks), the account's in PUT /v1/space-defaults {"harness": …}; null forgets. GET /v1/spaces/{id} shows settings.harness with its source (space, account, none). GET /v1/agents lists what the node's image has. SDK Spaces (profile: "sdk") never get a harness: the account's default does not reach them and setting one is refused (invalid_request, reason: sdk_profile).

Your repository: --repo#

shark code --repo acme/api                         # owner/name = github.com; or any https:// clone URL
shark code --repo acme/api --branch develop        # start from that branch (default: the repository's default)
shark code --repo acme/api --work-branch fix-login # work on that branch (default: shark/<space>; created if missing)
  • The clone runs once, as one command in the Space (through its egress, like any command), into the Space's root. Run shark code --repo … again and the clone is kept as it is; a Space that already holds another repository is refused (clone_failed, with the fix).
  • Your git config user.name / user.email from this machine are set in the clone (if you have them).
  • Git credentials never enter the VM. With the Shark GitHub App connected (shark github connect, coming), the CLI stores a token for that one repository (contents and pull requests, ≤ 1 h, refreshed every 45 min while shark code runs) as the Space's GITHUB_TOKEN inject secret. Without it, a public clone works, and for pushes: gh auth token | shark secrets set <space> GITHUB_TOKEN --preset github. In both cases the clone's git credential helper hands git the placeholder, and the egress proxy swaps in the token only toward github.com and api.github.com.

Agents and versions#

--agent agent version in the session VM image
claude Claude Code 2.1.318
codex Codex CLI 0.166.0
opencode OpenCode 1.19.5 (2.x is not supported yet)

The versions are pinned in the image (they do not self-update); a new version ships only after the same checks pass with it. There is no built-in default agent: see Choosing the agent.

First start. With an API key, Claude Code skips its first-run setup and asks one question: whether you trust the folder (once per Space; it is remembered). Codex and OpenCode open ready. With --auth subscription, each agent shows its own login the first time.

shark run-agent runs the agent unattended: it approves the agent's own permission prompts (Claude Code --permission-mode bypassPermissions, Codex without approvals, OpenCode --auto) and prints the agent's JSON events. Every command it runs still goes to its own microVM, as always. Interactive shark code keeps the agent's normal approval prompts.

How it works#

  • The agent runs in the Space's session VM. Its shell is Shark's shim: every command the agent runs (npm test, git, builds) runs in its own fresh microVM next to your files, exactly as with shark run. The session VM only holds the agent itself.
  • Your terminal talks to the agent over the same authenticated channel as the rest of the API (shark-pty.v2). A browser can attach the same way over WebSocket (protocol: docs/spec/v1/shark-pty-v2.md).
  • States in shark ls: working, waiting-for-you (it is asking you something or finished), idle, asleep.
  • Notifications: when the agent waits for you and nobody is attached, Shark can call your webhook, send a Telegram message or an email (configured per account; the message never contains anything from the screen).

The agent and the terminal#

shark code starts the agent. shark attach <space> (when no agent runs, or with --shell or -- cmd) opens a terminal: a login shell (bash -l) in the session VM, as the Space's user, in the Space's root. It never starts an agent by itself.

  • Commands you type in the terminal run in the session VM (2 vCPUs, the Space's files), like on any machine. The agent's commands go each to its own microVM. Use shark run or the agent for heavy work.
  • An agent you start by hand in the terminal (claude, codex, opencode typed at the prompt, or after the agent exits) runs as under shark code: a small wrapper Shark puts first on the terminal's PATH gives it Shark's shim as its shell (each command in its own microVM), its config and login in the VM's memory-only directory (never in the Space's files, commits or forks), and Shark's context ($SHARK_CONTEXT, its instructions file). The terminal itself also carries the few variables only these agents read (CLAUDE_CODE_SHELL, CLAUDE_CONFIG_DIR, CODEX_HOME), so Claude Code started by its full path routes too; your shell's $SHELL, $HOME and XDG_* stay yours (Shark's shim refuses interactive shells, so it is never your $SHELL).
  • A login made this way is saved to your encrypted store on the node only while a shark code --auth subscription session of the same agent runs in the Space (they share the directory); otherwise it stays in the VM's memory and is gone when the VM is replaced. For a login that lasts, use shark code --auth subscription. When a shark code agent stops, its directory is erased, including a login made by hand.
  • Not routed: an agent your login profile puts ahead of Shark's wrapper on PATH (Codex and OpenCode then run their commands in the session VM; Claude Code still routes and keeps its login out of the Space through the variables above), or an image without Shark's shim (the wrapper says so on start).

Conversations: --continue, --resume, --sessions#

The agent's conversations are kept in the Space, so the next shark code can pick one up, after the agent exits, the VM sleeps or restarts, or the node restarts:

$ shark code --continue myproj                 # the last conversation (claude --continue)
$ shark code --resume myproj                   # the agent's own picker (Claude Code, Codex)
$ shark code --resume 0b4f3c2e-1d5a-4e6f-8a9b-0c1d2e3f4a5b myproj
$ shark code --sessions myproj                 # past conversations: id, start, last activity, size
ID                                    STARTED           LAST ACTIVITY          SIZE
0b4f3c2e-1d5a-4e6f-8a9b-0c1d2e3f4a5b  2026-11-06 10:00  2026-11-06 11:42   1.2 MiB
$ shark code --sessions myproj --json          # {"sessions":[{"id","started","last_activity","bytes"}],"bytes",...}
--continue --resume [ID] --sessions
Claude Code claude --continue claude --resume [ID] yes
Codex codex resume --last codex resume [ID] yes
OpenCode opencode --continue opencode --session ID (an ID is required: error resume_needs_id) one database: opencode session list in a terminal
  • An id that is not a UUID (a Codex thread name): --resume=NAME. For an agent this shark does not know, the error is resume_unsupported; pass the agent's own flag after --.
  • Both flags apply when the agent starts. If it is already running, shark code joins it as it is and says so; shark stop myproj first to start it on another conversation.
  • Where: <space>/.shark/agents/<agent>/ (Claude Code: projects/ transcripts and the todo, task and plan files they refer to; Codex: sessions/; OpenCode: opencode.db), linked from the agent's config directory at every start, also for an agent you start by hand in a terminal. .shark is Shark's hidden directory of the Space (where its tool caches live): left out of the root's listings and of the files API, so git add -A does not pick it up. A fork copies it: the fork's agent can continue the same conversation.
  • Never credentials: only these paths are kept in the Space. The agent's login (.credentials.json, Codex's and OpenCode's auth.json) and settings stay in the VM's memory and your encrypted store, as before. Checked with the real agents: the API key they ran with is in none of the kept files.
  • Size: a long conversation is a few MB, and transcripts only grow. They count toward the Space's storage like any file; snapshots are deduplicated by content, so an append stores the new part, not a new copy. Claude Code deletes its own transcripts older than 30 days (its cleanupPeriodDays setting). Delete old ones from a terminal of the Space (rm .shark/agents/claude/.claude/projects/*/ID.jsonl); --sessions shows sizes.
  • The listing reads the Space's last commit (taken every 30 s while a terminal is attached, and when it leaves), and never shows what a conversation says. API: GET /v1/spaces/{id}/agent/sessions?agent=claude (scope files:read).
  • Conversations from before this version were in the VM's memory and are gone.

If a start ends with [shark: the agent exited (code 125) …]: 125 is Shark's own code, not the agent's (the session VM, its terminal channel or the agent's start failed); the line says why. shark logs myproj shows what the agent printed, and shark code --continue myproj picks the conversation up again.

What the agent knows#

The agent works as on your laptop, and it is told where it runs and what it can and cannot do, so it does not try things that cannot work here and tells you the right thing. Validated locally only (no model was run: go test ./cmd/sharkfs -run TestAgentAware, ./internal/codeagent, ./cmd/shark -run TestCLIAgentContext); that each real agent reads these files is checked on metal next.

  • Instructions, written at every start from the copy inside Shark (never a stale one): Claude Code gets the Shark skill in its skills folder and a short CLAUDE.md in its config folder; Codex its global AGENTS.md; OpenCode its global AGENTS.md. Nothing is written into your repository. They end with a block about this session: your Space's name, whether previews exist on this node, how you add a key or allow a host.
  • $SHARK_CONTEXT in the agent and in every command it runs: a small JSON document (mode devbox, Space, what works: background processes yes as jobs bg-N in their own VM (shark ps / kill / logs), browser inside one command yes (shark screenshot where the image has one), a browser kept open no, exposed processes per node; the egress policy; how you add a secret; where usage is; profile (devbox or sdk); github (connected, or why and the fix the user runs, e.g. shark github connect); for a can or cli that is off, why and the command that turns it on, e.g. can_why.background_after_command). shark context --json prints it.
  • Your secrets and GitHub in the agent's commands. Every command the agent runs gets the Space's secrets as its other commands do: placeholders shk_ph_… the egress swaps on the way to each secret's hosts, the Space's CA, and with GitHub connected (shark github connect, once per account) GITHUB_TOKEN and GH_TOKEN (one placeholder) and a git setup (credential helper for https://github.com, git@github.com: rewritten to https, the connection's noreply commit identity). A connection made while the agent runs reaches its commands within about 15 s; a secret set while it runs, at its next command. The agent's own process (not its commands) sees the secrets of its start.
  • A shark CLI limited to this Space (on the hosted node; a self-hosted node sets -agent-api-url): the agent's commands get a credential minted by the node for this Space only, and their egress allows the node's API host. It can read this Space's usage, ops and logs, list, read and stop its background jobs (shark ps, shark logs bg-N, shark kill bg-N), list secret names, read the egress policy, start detached commands and expose ports where the node offers them. It cannot read your account balance, top up, manage keys, set secrets, change egress, touch other Spaces, delete or fork, or attach to the agent itself. It is valid 2 h, re-minted every hour while the agent runs, revoked when the agent stops, and never placed in the agent's own environment or instructions. A node restart invalidates it until the next start.
  • What it tells you. "How much credit is left?": it reads this Space's usage and tells you to run shark balance. It needs an API key: it asks you to run shark secrets set <space> NAME --preset … (hidden prompt), never for the key in chat. A blocked host: it reads shark egress denials <space> and relays shark egress use <space> +PROFILE (the profile that has the host), or shark egress add <space> HOST when none has it. A server that should stay up: an exposed process where the node has them; otherwise it says so. Shark's own notices in a command's output start with shark: (errors.md).

Egress: nothing to configure#

The agent runs in the Space's session VM under the Space's egress policy, and shark code --agent X turns on X's egress profile by itself: claude-code (the Anthropic API, the Claude login and token refresh, the model catalog), codex (the OpenAI API, the ChatGPT login and its backend) or opencode (its model catalog models.opencode.ai, which OpenCode fetches at every start, and its own Zen / Go service). With an API key, the key's provider profile goes on too (anthropic-api, openai-api), and shark-docs (these docs, sharkmagic.si) when the Space's list lacks it. Devbox Spaces already have essentials (package registries, GitHub, GitLab, Ubuntu mirrors, Docker Hub, Shark's docs). They show in shark egress show <space> as "added by shark itself"; shark egress use <space> -opencode removes one, PATCH …/egress {"auto_profiles": false} stops it. Another provider (OpenRouter, Gemini) is one more profile: shark egress use <space> +openrouter. The session VM takes a new policy at its next start (an idle one is restarted when the agent starts). See what was refused with shark egress denials <space>. Each profile's hosts, why each is there and how it was checked: shark egress profiles opencode.

Model credentials: two ways#

Store your provider key as an inject secret of the Space. The value is read from a hidden prompt or stdin, never from the command line:

shark secrets set myproj ANTHROPIC_API_KEY --preset anthropic --allow-egress
shark code myproj

(For Codex: shark secrets set myproj OPENAI_API_KEY --preset openai --allow-egress.) --allow-egress adds the provider's egress profile (anthropic-api, openai-api) to the Space. Without the key, shark code stops before starting the agent and prints this exact command (missing_key).

The agent sees a placeholder (shk_ph_…), never the key. Shark's egress proxy swaps the placeholder for the real key only on requests to the provider's API host. The key never enters the VM: code the agent runs (a malicious package, a prompt-injected command) cannot read it. A key stored as plain environment is removed from the agent's environment on purpose.

2. Your own subscription#

shark code --auth subscription myproj      # then log in inside the agent's UI, as on your laptop
  • Use of a subscription in a cloud machine is at your discretion and under your provider's terms. Check them before you do it; Shark does not decide this for you.
  • With a subscription, the login token does live inside the VM while the agent runs (unlike API-key mode), so code the agent runs could read it. Prefer API-key mode for untrusted repositories.
  • Where it is kept: the agent's login files are stored encrypted, per account, on the node, outside your Space. They are written to a memory-only directory of the VM when the agent starts, saved back when they change, and erased when the agent stops. They are never part of the Space's files, its commits, its backups or its forks.
  • While the VM is asleep, its memory image (including the token) is kept on the node's local disk; it is not uploaded.

Billing#

  • While the agent runs, the session VM is billed as an awake service (at a fraction of the command rate; see pricing). Commands the agent runs are billed as usual, per command.
  • After the agent has been waiting for you for 10 minutes (configurable), the session VM goes to sleep and costs nothing but storage. Your next key, or shark attach, wakes it.
  • "The VM sleeps while the model thinks" is not claimed: it is an open measurement.

Limits and what is not validated yet#

  • Real agents in real microVMs are validated on metal next. Locally, with the real agents: Claude Code's state hooks work, and all three send their shell commands through Shark's shim.
  • Codex's own sandbox is off in the session VM (each command's microVM is the sandbox). Codex finds its shell by name rather than $SHELL, so Shark puts its shim first on Codex's PATH; this relies on the agent's user having no login shell of its own in the VM, which is how Shark runs it.
  • The agent's state is detected from its screen (and from its hooks where it has them); a silent long-running tool call can read as waiting-for-you.