Cell - Ta Host
The server-side component of ta serve. It is functionally equivalent to Codex's app-server: pi only supports RPC mode over stdio (one process per session, one controller), and this component adds multi-session, multi-client, disconnect recovery, remote authorization, idempotent acknowledgments, and session locking. Command and event semantics are consistent with pi's RPC mode.
Components
| Component | Purpose |
|---|---|
TaServerImpl | @Rpc(TaServer), handles sessions and pi RPC commands. Each command is forwarded to AgentSession / AgentSessionRuntime following pi's rpc-mode approach (pi only exports runRpcMode bound to stdio, so each command is mapped here; verify against pi's rpc-mode when upgrading pi). |
TaWorkspaceServerImpl / TaTerminalServerImpl / TaSettingsServerImpl / TaPackageServerImpl / TaAuthServerImpl / RelayBridge | Other services on the same WebSocket; each forwards to the component below. TaAccess rejects HTTP-style calls (POST /api/<service> returns 404). |
TaSettingsServerImpl.setLanguage | Ta's pages tell the server their language (zh-CN, en-US), which becomes TA_LANGUAGE of the process; the pi packages of the server (ta-agents, ta-cron, ta-computer-use, /bug) speak it. The client that set it last wins |
ExtensionProvidersImpl | Model providers that pi packages register with pi.registerProvider (the agents of @celljs/ta-agents, for example) are in the shared model runtime only once extensions loaded. Before any session, TaAuthServerImpl.listProviders, the model list of the settings and openSession with a model first load the user's extensions once, in the home directory (project resources not trusted), so that no sign-in is asked for and a new conversation can pick their models. About 7 MB more than without, measured, and only on the first call |
TaAuthServerImpl | @Rpc(TaAuthServer): runs pi's /login flow on the server (OAuth login page, device code, or API key). It sends only the page to open and fields for the user to fill to the initiating connection (TaClient.onAuthEvent); the user's answer is sent back via answer. The logout method deletes stored credentials. Credentials are written to the server-side pi's auth.json and shared across all sessions; they are never sent back to the client. |
RelayBridge | @Rpc(TaRelayServer): reads relay.json from the agent directory. When paired devices exist, it connects to Relay and opens a connection to /api/ws on this machine for each device (see @celljs/ta-relay). The pair and unpair commands work the same as ta pair. If no Relay is provided, it uses https://relay.cellbang.com. |
TaWorkingDirectory | The working directory set by setWorkingDirectory, recorded in ta.json in the agent directory (workingDirectory), persists across restarts. Open sessions are unaffected. |
TaSessionRegistry | Manages open sessions. Rejects requests when exceeding maxSessions, closes sessions that are idle (no clients, not running, no pending dialogs) for longer than idleTimeoutMs. Session files are retained. |
TaSessionFactory | Assembles sessions the same way the pi command does: project trust, settings, extensions, themes. All sessions share one model runtime. Can be replaced by rebind. |
TaSession | One open session: event log, acknowledgments, connected clients. After newSession/fork/switchSession the id remains unchanged and broadcasts session_replaced. The navigateTree command (pi's /tree) also broadcasts when switching branches within the same session. |
RemoteUiContext | The pi extension's ctx.ui: dialogs are broadcast to all clients. The first response takes effect; if timed out, the default value from the extension is used. |
EventLog / ClientChannel | Each session retains recent events in bytes for reconnection recovery. Each client merges into a frame every 30ms and sends the next frame after the client acknowledges. Clients that fall too far behind are disconnected to force reconnection. |
TaAccess | Validates tokens (WebSocket handshake and HTTP), listens on localhost and validates the Host header to prevent DNS rebinding, writes serve.json. The token and port are saved to serve-state.json and reused on the next startup. |
WebExtensionCatalog(WebExtensionCatalogImpl) | listWebExtensions / readWebExtension / removeWebExtension: finds web extensions in packages using pi's package loading method (packages setting and extensions/ directory, project packages only when trusted). Reads only declared, in-package files that are under 2 MB. |
Packages (listPackages / installPackage / removePackage) | Lists all packages loaded by pi (with or without web extensions) and includes resources (extensions, web, skills, prompts, themes). The installPackage command works the same as ta install (project is -l, only for trusted projects). The removePackage command works the same as removeWebExtension and removes by package id. |
Package host service (callPackageHost) | Calls methods on objects returned by the built-in package pi.host (arguments and return values are JSON), for use by its web extensions. Methods that cannot be called are dispose, constructor, methods starting with _, or methods built into objects. |
Toggles (setPackageEnabled) | Ta's built-in pi packages (TaPackages) are toggled by name and listed with origin: 'builtin'. They can only be toggled, not removed. The toggle state is saved to ta.json in the agent directory (disabledPackages). Other packages are toggled by the id from listPackages and work the same as ta config, writing each resource individually to pi's settings (PackageSwitch: paths in extensions/ are written as +path / -path to the extensions list, items in packages are written to that item's filter). The terminal ta recognizes this as well. Packages with no pi loading and only web extensions are recorded in ta.json's disabledWeb. enabled: set to true when pi loads any resource from it (for web-only packages check disabledWeb). Disabled packages' web extensions are not loaded. Takes effect after session reload. |
TaSettingsStore(TaSettingsStoreImpl) | getSettings / updateSettings: ta's settings (keys listed in TA_SETTINGS) are pi's settings.json, user-level in the agent directory, project-level in <cwd>/.ta/. Changes only the provided keys (set to null to delete; an empty object deletes the file thereafter). Preserves other content and uses atomic writes via temporary file. Only one write happens at a time. During writes, it acquires the same file lock as pi (<file>.lock directory, considered stale after 10 seconds), so the terminal ta changing settings simultaneously does not overwrite (pi rereads within the lock and only writes back the keys it changed). Does not overwrite unreadable files. At project level, pi requests trust for projects that have not been trusted (with extensions, skills, etc.). Writing without requiring trust is treated as trusting (otherwise pi will not read). After writing, it notifies related sessions' SettingsManager to reload (model and reasoning level take effect for new sessions). |
ExtensionDataStore(ExtensionDataStoreImpl) | getExtensionData / setExtensionData: data stored on the server for packages (web extensions' pi.storage). One file per package at <agentDir>/extension-data/<package name>.json (mode 0600), with serial writes to the same file. Set values to null to delete keys. 4 MB limit per package. When cwd is provided, it is data for that directory at extension-data/directories/<dir name>-<hash>/<package name>.json. The hash includes the execution environment, so same-name directories do not interfere when running remotely. Notifications include cwd. |
ClientToolBroker | Client tools (clientTools): each is a pi tool that broadcasts client_tool_request on call and waits for the first client to respond via respondClientTool. Ends with failure if the run is aborted or the session closes. |
DefaultExecEnvironmentFactory | Tools execute based on directory: ssh://<host>/<path> runs on that host (RemoteHosts); local paths run locally; when ta.exec.url is configured, tools run on ta exec-server (see @celljs/ta-exec). The environment's cwd is the path in that environment; all paths resolve by it. A custom ExecEnvironmentFactory can also be used via rebind. |
RemoteHosts / TaRemoteServerImpl | @Rpc(TaRemoteServer): hosts for ssh:// directories, each with an SshHost (uses system ssh and its config; auto-installs Node.js and the server on first use), connects on first call and reconnects after disconnection. Provides listHosts (Hosts from ~/.ssh/config without wildcards and known to ssh, plus from ta.json's remoteHosts), addHost / removeHost (remember or forget a host), resolveLocation (expand location without path to home directory), watch / answer (when ssh needs password, confirmation, or new host, asks all pages; first response wins), disconnect. Pushes connection steps and changes to TaClient.onRemoteEvent. |
PortForwards | forwardPort: opens ports from the host of a remote directory on localhost's 127.0.0.1 (trying to reuse the same port number), with each connection forwarded through the execution environment's connect. Local directories are returned as-is. |
PiDirectories | Remote directories (and all directories when ta.exec.url is set) use shadow directories on localhost: <agent directory>/remote/ssh/<host>/<path>. Pi names session files by them and writes project settings there; session info then switches back to the location. Files like AGENTS.md from remote projects are read by RemoteContextFiles and provided to pi (see ta-remote-ssh.md). |
Each session loads extensions the same way as the ta command: pi's built-in codemode, tool search, and MCP (pi's SDK does not auto-load them; when the execution environment is not local, stdio MCP services start in the execution environment). Ta's built-in extensions, enabled built-in packages' pi extensions, and AgentExtension and AgentTool components (@celljs/ai-coding-agent).
Configuration
ta:
host:
maxSessions: 32
idleTimeoutMs: 600000
eventBufferBytes: 524288 # per-session event amount for recovery
clientBacklogBytes: 8388608 # per-client backlog allowed
exec:
url: '' # address of ta exec-server (ta serve --exec); empty: local
token: '' # ta serve will fill TA_EXEC_TOKEN here
remote:
ssh: ssh # ssh command for ssh:// directories; ~/.ssh/config applies normally
sshArgs: [] # prepended to each host, e.g. ["-F", "/path/config"]
nodeMirror: '' # where to download Node.js if the host lacks 18+; empty: https://nodejs.org/distWhen @celljs/ta-web is installed, the / path on the same port serves the Web client: GET requests for page files do not require a token (they contain no data), and all calls under /api require a token. When listening on localhost, both validate the Host header.
Session Storage (Cloud)
ta serve locally uses pi's session files, relying on session locks to ensure only one process writes at a time. In the cloud there may be multiple ta serve replicas, any of which may take over a session. In this case, the session is stored in TaSessionStorage:
- Lease + epoch:
acquireincrements the epoch when obtaining a session. Eachwriteincludes the lease, and requests are rejected if the epoch is not current (StaleLeaseError), so nothing is written. The old holder, even if not yet aware of being taken over by another replica (takeOveror after lease expiration), cannot write. The expiration time only determines when others can take it without going through takeover. FileSessionStorage:<id>.jsonl(pi's format) and<id>.lease.jsonin a directory, using lock directories for atomicity. Can be used locally or on a shared filesystem, and also serves as a reference for cloud implementations (conditional database updates, compare-and-swap in KV stores) to follow.StoredSession: pi'sSessionManager(in-memory mode) plus write-through to storage. Each time pi adds a record, it is written out in order, the same as pi writing to a file. Nothing is written until the first user or model message. The lease is renewed at one-third intervals. After the lease is lost, pi adding records fails (like file write failure), andonLostnotifies the host to stop the session.
Which cloud storage to use has not yet been decided, so TaSessionFactory has not integrated it yet. Pi's AgentSessionRuntime still creates SessionManager by file when creating new conversations, forking, or switching sessions. When integration happens, these places will switch to StoredSession.
Known Limitations
- When there are no available models (model service not signed in, no model selected), pi's error will mention the
/logincommand and its documentation path in the terminal. This is changed here toCELL_TA_NOT_SIGNED_INwith a prompt to sign in first usingTaAuthServer. - The deduplication table, event log, and pending dialogs are all in memory. After service restart, dialogs become invalid (extensions receive the default value), and clients should call
getMessagesfirst to verify before deciding to resend. - When executing remotely, pi's resources (
AGENTS.md, project.ta/settings and skills) still read from the same paths on the host machine. Project resources on the execution machine rely on a custom ResourceLoader (not yet implemented).