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.
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
LocalExecEnvironment | This machine |
RemoteExecEnvironment + ExecClient | ta exec-server on another machine (container, sandbox) |
ExecServer | ta exec-server itself: an HTTP service that doesn't load Cell (~60 MB when empty) |
ExecStdio | The 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. |
SshHost | Connects 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. |
SshConfig | Hosts in ~/.ssh/config (including Include) that don't use wildcards. |
# 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/execProtocol (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: Beareror?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;
ExecClientreconnects 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.