Skip to content

Cell - Ta Exec ​

Where the agent runs commands and reads/writes files—similar to Codex's exec-server: the agent loop stays in the host, and command and file operations go to the execution environment. Independent of pi.

ts
interface ExecEnvironment {
    readonly cwd: string;     // path in the execution environment
    readonly local: boolean;  // whether it is this machine
    exec(command, options?): Promise<ExecResult>;       // one-off execution; streams output; kills the process group on timeout/abort
    spawn(command, args, options?): ExecProcess;        // long-lived process; stdio communication (MCP, LSP)
    readFile / writeFile / stat / readdir / mkdir / remove
    connect?(port, host?): Promise<ExecSocket>;         // TCP connection on the execution environment's machine (only for non-local environments); used for port forwarding
}

All paths are paths in the execution environment. Never pass them to node:fs, and give the model only these paths.

Implementation ​

LocalExecEnvironmentThis machine
RemoteExecEnvironment + ExecClientta exec-server on another machine (container, sandbox)
ExecServerta exec-server itself: an HTTP service that doesn't load Cell (~60 MB when empty)
ExecStdioThe same server runs on stdin/stdout (one message per line), serving only one host. Ends when input closes or 60 seconds pass without messages (host pings every 15 seconds), terminating everything it started.
SshHostConnects to another machine via system ssh: uses its own ControlMaster for one login, other commands share it. Ssh prompts for the user go through SSH_ASKPASS to ask. Detects the host; if it lacks Node.js 18+, downloads it locally (verified by SHASUMS256) and installs to ~/.ta/server/node over ssh. Places EXEC_REMOTE_SOURCE (the ExecStdio bundled in scripts/remote-server.ts, ~7 KB, named by hash) in ~/.ta/server/<hash>/ and starts it in the user's login shell; client is its ExecClient.
SshConfigHosts in ~/.ssh/config (including Include) that don't use wildcards.
bash
# execution machine (container)
TA_EXEC_TOKEN=... ta exec-server --host 0.0.0.0 --port 4200 --cwd /workspace
# host
TA_EXEC_TOKEN=... ta serve --exec ws://exec-host:4200/exec

Protocol (ExecRpc) mirrors Codex exec-server: JSON-RPC 2.0 over WebSocket (or stdin/stdout, one message per line). Methods include process/exec, process/spawn, process/write, process/kill, fs/readFile, fs/writeFile, fs/stat, fs/readdir, fs/mkdir, fs/remove, net/connect, net/write, net/close. Process output is pushed as process/output, process/stderr, process/exit notifications; connection data is pushed as net/data, net/closed notifications. Bytes are base64-encoded. ExecClient doesn't care about transport: ExecClient.webSocket(url, token) or an SshHost provides the connection.

  • Auth: Token (via Authorization: Bearer or ?token=). The security boundary is the execution machine itself (like pi: no approval before each tool call, just OS/container isolation).
  • Environment variables: Only new or changed variables on top of the host's environment are sent (RemoteExecEnvironment.delta); host variables (API keys, etc.) never reach the execution machine.
  • Epoch isolation: The host connects with an epoch (default: host startup time). If a larger epoch connects, the execution machine closes connections and kills processes from smaller epochs; their subsequent calls fail with CELL_TA_EXEC_STALE_EPOCH. After a session moves to a new host instance, the old one cannot run tools.
  • Process lifecycle: Processes belong to their connection. Disconnecting ends processes started by that connection; ExecClient reconnects on the next call.

Host-side pi built-in tools (read, bash, edit, write, ls, find, and ! commands) and stdio MCP servers all run through the execution environment (see ExecExtension and ExecMcpTransport in @celljs/ta-harness). The ripgrep version of grep works only locally—remote sessions drop it, and the model uses bash rg instead.