An operating environment for AI coding agents.
TermAl gives you one place to run, supervise, review, and steer long-running software work performed by AI agents — structured around real agent workflows instead of raw terminal output.
?workspace=planner on one screen, ?workspace=review on another)~/.termal/termal.sqlite)TermAl integrates with Claude Code, OpenAI Codex, Gemini CLI, Cursor, and OpenCode. Agent protocol differences are normalized into the same session, message, approval, and streaming model in the UI.
Browser (React + TypeScript) Rust Backend (axum + tokio)
┌──────────────────────┐ ┌──────────────────────────────────┐
│ Workspace │ SSE │ AppState │
│ ├── Split panes │◄═══════════╡ ├── Sessions + message history │
│ ├── Session chat │ REST │ ├── Agent child processes │
│ ├── Diff viewer │───────────>│ ├── Approval queues │
│ ├── Source editor │ │ ├── Remote registry │
│ ├── Control panel │ │ ├── Orchestrator templates │
│ ├── Orchestrator │ │ ├── Workspace layouts │
│ ├── Filesystem │ │ └── Persistence (~/.termal/) │
│ └── Git status │ │ │
└──────────────────────┘ │ Agent Runtimes │
│ ├── Claude (NDJSON stdio) │
│ ├── Codex (JSON-RPC stdio) │
│ ├── Gemini (ACP stdio) │
│ ├── Cursor (ACP stdio) │
│ └── OpenCode (ACP stdio) │
└──────────────────────────────────┘
:8787. Spawns agents as child processes, communicates via stdin/stdout.:4173 (dev). State is split across focused app hooks, local stores, and typed API/live-update helpers; no Redux-style external state library.~/.termal/termal.sqlite; orchestrator templates in ~/.termal/orchestrators.json.TermAl can manage agent sessions on remote machines over SSH. The browser always connects to the local backend; remote operations are transparently proxied through persistent SSH tunnels.
TermAl currently launches SSH non-interactively, so the simplest setup is
key-based auth with your normal system ssh / ssh-agent flow.
If you do not already have an SSH key on your laptop:
ssh-keygen -t ed25519
Install that public key on the remote machine:
ssh-copy-id user@host
On Windows PowerShell, the equivalent is:
type $env:USERPROFILE\.ssh\id_rsa.pub | ssh {IP-ADDRESS-OR-FQDN} "cat >> .ssh/authorized_keys"
If you generated an Ed25519 key above, use id_ed25519.pub instead of id_rsa.pub.
Then verify normal SSH works before configuring the remote in TermAl:
ssh user@host
Notes:
ssh-copy-id copies your public key into the remote account’s
~/.ssh/authorized_keys.ssh-copy-id is unavailable, append the contents of
~/.ssh/id_ed25519.pub to ~/.ssh/authorized_keys on the remote manually.Your laptop Remote build server
┌──────────────┐ ┌────────────────────────┐ ┌────────────────────────┐
│ │ SSE │ Local TermAl (:8787) │ SSH │ Remote TermAl (:8787) │
│ Browser │◄═══════╡ │ tunnel │ │
│ │ REST │ RemoteRegistry │───────>│ Agent Runtimes │
│ │───────>│ ├── build-box ───────────────> │ ├── Claude │
│ │ │ └── gpu-server ─ ... │ │ ├── Codex │
└──────────────┘ └────────────────────────┘ │ └── Gemini │
└────────────────────────┘
ssh -L {local_port}:127.0.0.1:8787) and optionally starts a TermAl server process on the remote host./api/events stream, merging remote state and delta events into the local state so the UI stays live.Local port (47000–56999) Remote host
│ │
▼ ▼
┌─────────┐ ssh -L 47001:127.0.0.1:8787 user@build-box ┌─────────┐
│ :47001 │ ─────────────────────────────────────────────────> │ :8787 │
└─────────┘ encrypted tunnel └─────────┘
│ │
Local TermAl Remote TermAl
proxies REST + SSE runs agents
through this port on remote fs
Each remote gets a dedicated local port from the 47000–56999 range. The tunnel carries all traffic: REST API calls, SSE event streams, and health checks.
Two startup modes:
| Mode | SSH command | When used |
|---|---|---|
| Managed server | ssh ... user@host termal server |
Default — starts a TermAl server on the remote |
| Tunnel only | ssh -N ... |
Fallback — assumes TermAl is already running remotely |
TermAl tries Managed server first (15 s timeout). If it fails (e.g. termal not installed on the remote), it falls back to Tunnel only mode automatically.
Remotes are bound at the project level, not per-session or per-action:
Project "backend-api" ── remote: build-box
├── Session 1 (proxied to build-box)
├── Session 2 (proxied to build-box)
└── Files / Git / Reviews (proxied to build-box)
Project "frontend" ── remote: local
├── Session 3 (runs locally)
└── Files / Git / Reviews (local filesystem)
When you create a session inside a remote-bound project, TermAl:
POST /api/projects if needed).All subsequent operations — sending messages, approving actions, browsing files, viewing git status — route through the same tunnel automatically.
Remote TermAl Local TermAl Browser
│ │ │
│── SSE state ──────────────────>│ │
│ (full snapshot) │── merge + rewrite IDs ─────>│
│ │ (local session IDs) │
│── SSE delta ──────────────────>│ │
│ (text chunk, command update) │── apply delta + publish ───>│
│ │ (forwarded as local delta) │
│ │ │
│<── REST (proxied) ────────────│<── REST ─────────────────────│
│ POST /api/sessions/3/messages│ POST /api/sessions/5/messages
│ (remote session ID) │ (local session ID) │
The local backend is the single source of truth for the browser. It rewrites session and project IDs so the frontend sees a unified namespace — remote sessions look identical to local ones.
SSH remotes currently store only the connection settings needed to reach the machine:
When a remote is used, TermAl first tries to start termal server over SSH. If
that fails, it falls back to tunnel-only mode and expects a TermAl server to
already be running on the remote host.
| Setting | Description | Default |
|---|---|---|
name |
Display name for the remote | (required) |
host |
SSH hostname or IP | (required for SSH) |
user |
SSH username | (current user) |
port |
SSH port | 22 |
enabled |
Whether the remote is available for new projects | true |
Remotes are configured in Settings > Remotes and persisted in ~/.termal/termal.sqlite alongside session data. A built-in local remote is always present and cannot be removed.
.nvmrc for version)cargo run
The server starts on http://localhost:8787.
cd ui
npm install
npm run dev
The UI opens on http://localhost:4173. API calls are proxied to the backend automatically.
cargo run -- --repl
Runs an interactive terminal loop — reads prompts from stdin, runs one agent turn at a time. Useful for testing.
Configure Telegram from Settings -> Telegram:
@BotFather and copy the bot token./start.The backend runs the relay in-process from saved Settings -> Telegram
configuration. Today the relay is bound to one Telegram bot/chat and can control
multiple subscribed TermAl projects from that chat. The planned multi-bot model
is documented in docs/features/telegram-ui-integration.md: each bot becomes a
named profile with its own token, linked chat, project subscriptions, defaults,
and relay runtime.
Telegram commands:
/projects — list subscribed projects/project <id> — switch the active project for this Telegram chat/sessions — list sessions in the active project by name, active first and then by latest update/session <name> — select a session inside the active project by exact name or id/session clear — return free text to the latest promptable root session in the active projectProject digests and digest actions are temporarily disabled. Free text is forwarded into the selected session, or into the latest promptable root session in the active project when no session is selected. Assistant replies from the selected session are tailed back to Telegram after they settle only when Forward assistant replies is enabled; that setting can send full assistant output, including code, file paths, file contents, or secrets, to Telegram.
termal/
├── src/
│ ├── main.rs # Entry point and CLI
│ ├── api.rs # Axum HTTP routes
│ ├── api_*.rs # Route groups for files, git, review, SSE, etc.
│ ├── state*.rs # AppState, boot, accessors, and persistence-facing state
│ ├── session_*.rs # Session lifecycle, messages, config, runtime, and sync
│ ├── claude*.rs # Claude runtime integration
│ ├── codex*.rs # Codex runtime, RPC, events, and thread actions
│ ├── gemini.rs / acp.rs # Gemini and ACP runtime integration
│ ├── remote*.rs # SSH tunnels, remote routing, proxies, and sync
│ ├── orchestrator*.rs # Orchestrator templates, instances, and transitions
│ ├── terminal*.rs # Terminal run/stream support
│ ├── wire*.rs # HTTP/SSE wire types
│ └── tests/ # Backend test modules
├── ui/
│ ├── src/
│ │ ├── App.tsx # App composition
│ │ ├── app-*.ts # Focused app hooks and state orchestration
│ │ ├── api.ts / types.ts # API client and shared TypeScript types
│ │ ├── live-updates.ts # SSE delta event application
│ │ ├── session-store.ts # Session slice publication for heavy panels
│ │ ├── workspace*.ts # Pane/tab/split state and layout persistence
│ │ ├── message-*.tsx # Message cards, icons, and render helpers
│ │ ├── themes/ # 21 color themes + chrome style presets
│ │ └── panels/
│ │ ├── AgentSessionPanel.tsx # Chat session view
│ │ ├── ControlPanelSurface.tsx # Dockable sidebar with section tabs
│ │ ├── OrchestratorTemplatesPanel.tsx # Visual canvas editor for workflows
│ │ ├── OrchestratorTemplateLibraryPanel.tsx # Template library and instance management
│ │ ├── SessionCanvasPanel.tsx # Session graph overview
│ │ ├── SourcePanel.tsx # Source file viewer
│ │ ├── DiffPanel.tsx # Diff viewer
│ │ ├── FileSystemPanel.tsx # Filesystem browser
│ │ ├── GitStatusPanel.tsx # Git status and diff tree
│ │ ├── ResponseBoardPanel.tsx # Spatial board of pinned agent responses
│ │ ├── MailboxPanel.tsx # Durable agent mailbox reader
│ │ ├── TerminalPanel.tsx # Scoped terminal command runner
│ │ └── InstructionDebuggerPanel.tsx # Agent instruction tracing
│ └── vite.config.ts # Dev proxy: /api → :8787
├── docs/
│ ├── architecture.md # Full architecture reference
│ ├── vision.md # Product vision
│ ├── roadmap.md # Phased roadmap
│ └── features/ # Feature briefs (orchestration, workspaces, agent integrations, etc.)
├── Cargo.toml
└── Cargo.lock
The orchestrator lets you design reusable multi-agent workflows as directed graphs. Each node is an agent session; each edge is a transition that fires when a session completes.
Open the orchestrator canvas to build a workflow visually:
OnCompletion), result mode, and optional prompt template~/.termal/orchestrators.json and reusable across projectsLaunch an orchestrator instance from a template. TermAl creates the sessions, starts the first one, and then:
prompt_ready, the orchestrator evaluates outgoing transitions.Instances can be paused, resumed, or stopped from the control panel or via REST API.
| Setting | Options | Description |
|---|---|---|
| Trigger | OnCompletion |
Fires when the source session finishes its turn |
| Result mode | None, LastResponse, Summary, SummaryAndLastResponse |
What context to include in the delivered prompt |
| Input mode | Queue, Consolidate |
How multiple inbound transitions are handled |
| Prompt template | Free text with `` placeholder | Custom prompt wrapping the result |
docs/architecture.md — system design, API reference, agent protocol detailsdocs/vision.md — product framing and guiding principlesdocs/roadmap.md — phased roadmapdocs/themes.md — current theme, chrome style, font, and density systemdocs/test.md — current backend/frontend test strategydocs/features/ — feature briefs including: