Skip to content

Latest commit

 

History

History
1805 lines (1313 loc) · 236 KB

DOCUMENTATION.md

File metadata and controls

1805 lines (1313 loc) · 236 KB

Termpolis Documentation

The definitive guide to Termpolis — Secure AI-Assisted Development. The local-first multi-agent terminal where Claude, Codex, and Gemini work as a team, coordinated by a dedicated AI conductor, without your source code leaving the machine.

This document covers installation, the AI Security Center, the share-to-Slack/Teams workflow, every feature, every panel, every keyboard shortcut, and the architecture behind the swarm. Screenshots live in ../e2e/screenshots/docs/ and are mirrored to the website at termpolis-web/docs/screenshots/.


Table of Contents

  1. Overview
  2. Installation
  3. First Launch & Welcome Screen
  4. The Sidebar
  5. Terminals
  6. Tab & Split Views
  7. Settings
  8. Themes
  9. Keybindings
  10. Agent Capability Ratings
  11. Command Palette
  12. Prompt Templates
  13. Workflow Orchestrator
  14. Context Panel
  15. History Search
  16. Conversation Search
  17. Git Panel
  18. AI Agent Profiles
  19. MCP Server
  20. Swarm Dashboard
  21. AI Conductor
  22. Activity Feed
  23. Intervention Controls
  24. Swarm Review Panel
  25. Persistent Memory
  26. Observability
  27. Status Bar
  28. Troubleshooting
  29. Termpolis Remote (phone app)
  30. Linked machines
  31. Architecture
  32. Keyboard Shortcut Reference

1. Overview

Welcome screen

Termpolis is a cross-platform desktop terminal manager (Windows, macOS, Linux) built on Electron + React + TypeScript with node-pty powering the underlying shells. It ships as a native app — code signed on Windows, notarized on macOS.

What makes it different:

  • Secure AI-Assisted Development: a built-in AI Security Center (Settings → AI Security) auto-scans every AI prompt against 90+ secret patterns (AWS, GitHub, Azure, GCP, Stripe, Slack, JWT, PEM, …), can block free-tier Gemini launches (Strict Mode, off by default), keeps a local JSONL audit log, and surfaces per-provider training-disposition facts sourced from live ToS pages. See the Security section.
  • Multi-agent swarm: Claude Code, Codex, and Gemini CLI work together on a task. A dedicated Claude Code instance acts as the conductor.
  • MCP server baked in: AI agents can control Termpolis via Model Context Protocol — open terminals, run commands, send messages.
  • Transparent routing: every subtask shows which agent got it, why, and what it cost.
  • Activity observability: every token, every tool call, every message from every agent is visible in real time.
  • Intervention controls: pause, cancel, or steer any agent mid-task without leaving the feed.
  • Shared memory: a RAG-backed memory store that any agent can read and write via MCP.
  • MCP-native end to end: all three agents speak MCP. Tasks are completed only by the conductor (swarm_update_task) or by you (Done/Fail in the Tasks tab), never by what an agent prints; for Codex and Gemini workers Termpolis also passes what their screens show to the conductor, as hints.
  • Share-ready output: three fixed copy shortcuts turn any terminal selection into plain text, a Slack/Teams-ready message, or a code block for a PR. See Copy for Slack / Teams / PRs.

Everything is built around the idea that you're not writing code alone anymore — you're orchestrating a team, and you need the tools to do it well, securely.

Security Center

The AI Security Center at Settings → AI Security is the security backbone of Termpolis. Every check runs on the local machine. None of these features send data to Termpolis or any third party.

  • Per-provider training-disposition facts. Live ToS-sourced summaries: Claude (default off), Codex (default off), Gemini paid (excluded), Gemini free OAuth (Google may use prompts, flagged yellow).
  • Prompt watching (always on). Once you launch claude, codex, or gemini in a terminal, every Enter and every paste-sized chunk (32+ characters) is scanned in main-process memory against the same secret rules Commit Shield uses. The scan works on a copy: your text reaches the agent unchanged, never modified, delayed or held back. A hit is recorded in the audit log as a prompt_secret_sent event (the rule and the variable name, never the value) and shown in a dismissable banner that names what was sent, so you know what to rotate. The catalog covers AWS (access/secret/session), GitHub (classic/fine-grained/OAuth/runner), GitLab, Bitbucket, Azure (Storage, SAS, conn-string, AD client secret, DevOps PAT), GCP (SA JSON, OAuth client), AI providers (OpenAI, Anthropic, Google, HuggingFace, Cohere, Replicate), payments (Stripe, PayPal Braintree, Square), comms (Slack, Discord, Telegram, Twilio, SendGrid, Mailgun, Mailchimp, Postmark), cloud (Cloudflare, DigitalOcean, Heroku, Netlify, Vercel, Fly.io, Render, Pulumi), CI/CD (CircleCI, Travis, Codecov), observability (Sentry DSN, Datadog, New Relic, Rollbar, Honeycomb, Mapbox, Okta, Auth0), package registries (npm, PyPI, Docker Hub), secrets vaults (HashiCorp Vault, Doppler, 1Password Connect), database connection strings (Postgres/MySQL/MongoDB/Redis), HTTP basic-auth URLs, JWTs, PEM/GPG private key blocks, and the .env-style catch-all. Non-AI terminals are not scanned (zero overhead). A manual paste-and-scan box is also available in the Settings panel.
  • Gemini account-mode auto-detection. Reads GEMINI_API_KEY, GOOGLE_API_KEY, GOOGLE_GENAI_USE_GCA, and GOOGLE_APPLICATION_CREDENTIALS+GOOGLE_CLOUD_PROJECT to identify which tier the Gemini CLI will hit (Vertex / Code Assist / Paid API key / Free OAuth).
  • Strict Mode — block free-tier Gemini. When ON, Termpolis intercepts shell-level gemini invocations and refuses to forward them unless paid-tier credentials are detected. Blocked launches are recorded in the audit log as BLOCKED: strict-mode + free-tier.
  • Local JSONL audit log. On by default: AI-agent terminal launches and closes, secrets sent in a prompt, Commit Shield results and other security events are appended to ai-security-audit.jsonl in the data directory. Append-only, rotated at 10 MB. You can turn it off or wipe it in Settings → AI Security.
  • Legal disclaimer. Apache 2.0 "AS IS". Full disclaimer in TERMS.md §5a and inline in Settings → AI Security.

Copy for Slack / Teams / PRs

Copying is keyboard-driven: select text in a terminal, then press one of three shortcuts. They are fixed (they can't be remapped), and the terminal's right-click menu lists them as a reminder.

  • Copy (Ctrl+Shift+C) — the selection as plain text.
  • Copy for Teams/Slack (Ctrl+Shift+K) — the selection as a chat message: tight line breaks, emoji kept, no code box.
  • Copy as Code Block (Ctrl+Shift+Q) — the selection as a code block: formatted HTML plus a fenced markdown copy, so it pastes cleanly into Slack, Teams, GitHub, GitLab or Notion.

2. Installation

Download

Grab the latest build from the Termpolis website or directly from GitHub Releases.

Platform File Signed
Windows termpolis-setup-<ver>.exe ✅ Code-signed
macOS (Apple Si) termpolis-<ver>-arm64.dmg ✅ Notarized
macOS (Intel) termpolis-<ver>.dmg ✅ Notarized
Linux termpolis-<ver>.AppImage —

Requirements

  • Windows: 10 or 11 (x64)
  • macOS: 12 (Monterey) or later — Apple Silicon and Intel builds both ship. 1.51.5 is the last version for macOS 10.15 and 11; auto-update won't offer them anything newer
  • Linux: AppImage runs on any modern glibc distro
  • Disk: ~200 MB
  • RAM: 512 MB minimum; 2 GB recommended when running multiple agents

First run

On first launch, Termpolis creates its data directory:

Platform Path
Windows %APPDATA%\termpolis\
macOS ~/Library/Application Support/termpolis/
Linux ~/.config/termpolis/

Inside you'll find session.json (your workspaces, tabs, and open terminals) and later swarm-memory.jsonl (shared agent memory). Delete either file to reset that layer without losing the app.


3. First Launch & Welcome Screen

Welcome screen — full

The welcome screen is where you start from when no terminals are open. It shows:

  • New Terminal — opens the new-terminal modal, where you pick the shell and theme.
  • Launch AI Agent — a picker for Claude Code, OpenAI Codex and Gemini CLI. Agents that aren't installed are marked Install; click one to see how to install it.
  • Start Swarm — coordinate several agents on a new or existing project (see Swarm Dashboard).
  • Tips and shortcuts — a line of highlights (the command palette, split panes, smart routing, the MCP server, session recording) and the shortcuts for the observability panels: activity feed, redundancy, efficiency and swarm.

Press Ctrl+Shift+T (⌘⇧T on macOS) to open the new-terminal modal from anywhere, or Ctrl+K for the command palette.

The first-run tour

The first time you open Termpolis, a six-step tour walks you through it: connecting your coding agents, what Termpolis is, setting an API key, launching your first agent or swarm, security, and your privacy choices.

  • Connect your coding agents (first step) shows exactly what Termpolis would change for each of Claude Code, Codex and Gemini CLI installed on this machine. It has two boxes, both ticked to start: one connects the agents, the other adds the optional SessionStart hook (Also load project memory when any Claude Code session starts). Nothing is written to an agent's settings until you finish or skip the tour — see Connecting your coding agents. The step also says that connecting lets Termpolis read Claude Code's saved notes into memory (read-only, switchable in Settings → Memory & Learning); the one-time review of an install connected by an older version lists the same read, and the read starts only when you answer Keep connected.
  • Your privacy choices (last step) has two separate boxes, one for crash reports and one for anonymous usage statistics, which start unticked, plus the switch for the Claude Code compression proxy.
  • Skip tour (or Esc) closes the tour at once and keeps the choices exactly as shown. On a first run, that means your agents are connected and nothing is sent.

Reopen it any time with Show tour again in the Help drawer. If you updated from a version before v1.49, Termpolis asks the two questions once instead, in two short reviews — your coding agents first, then privacy — and sends no crash reports or usage statistics until you answer.


4. The Sidebar

Sidebar — default state

The sidebar is the navigation spine of the app. From top to bottom:

  1. Toolbar — Settings, Split View / Tab View (switches between the two), Git Panel, Swarm Dashboard and Collapse sidebar. A collapsed sidebar shows only an Expand sidebar button.
  2. AI Agents — launch Claude Code, Codex or Gemini CLI, or add a custom profile with +.
  3. Workspaces — one row per saved workspace. Click one to reopen its set of terminals (see Workspaces).
  4. Workflows — your saved workflows, with Start Workflow to create one (see §13).
  5. Terminals — one row per open terminal, with + Add Terminal below the list.

Workspaces

Workspaces are saved sets of terminals: snapshots of your terminal layout that you can bring back with one click. They're handy for switching between projects, say a "Frontend" workspace with Node and build terminals and a "Backend" one with API and database terminals.

What a workspace saves: each terminal's name, shell, working directory, color, theme and font, and the AI agent it was running, if any. It doesn't save output or running processes, so restoring a workspace starts fresh sessions.

Saving a workspace. With at least one terminal open, click + Save Workspace in the sidebar's Workspaces section, give it a name, and press Save.

Restoring a workspace. Click a workspace to close the terminals you have open and reopen its saved set. Each terminal starts in its saved working directory, and a terminal that was running an AI agent starts that agent again.

Managing workspaces. Each workspace row has buttons to update it with your current terminals, rename it, and delete it. Workspaces are kept in session.json in the Termpolis data directory (see §2 for the per-platform path), with the rest of your session.

How workspaces differ from workflows. A workspace is a saved set of terminals; a workflow is a pipeline of steps the app executes for you. A workflow never rearranges your terminals — it runs its steps, streams their output, and records the result. See §13.


5. Terminals

New terminal modal

Every pane in Termpolis is a full pty-backed terminal powered by node-pty. That means xterm-compatible escapes, real TTY semantics, signal forwarding — not a shim.

Creating a terminal

Ctrl+Shift+T opens the New Terminal dialog (shown above). Fill in:

  • Name + color: help you tell terminals apart in split view.
  • Folder: where the terminal starts. It begins as the active terminal's folder. If you leave it empty, or the folder doesn't exist, the terminal starts in your home folder.
  • Shell: whichever your system has of PowerShell, Command Prompt and Git Bash on Windows, or Zsh, Bash and PowerShell on macOS and Linux. It begins as your default shell.
  • Font size, theme and font family: begin as the terminal defaults from Settings → General.

To open a terminal with an AI CLI already running, use Launch AI Agent instead. See AI Agent Profiles.

Running terminal

Terminal running

Once running, the terminal supports:

  • Copy with Ctrl+Shift+C, or with Ctrl+C while text is selected, and paste with Ctrl+V or Ctrl+Shift+V. On macOS, ⌘C and ⌘V.
  • Mouse scroll, and links you can click to open.
  • Full 256-color + truecolor palettes.
  • Right-click for a context menu: the three copy shortcuts, Paste, Select All, Find..., Export Full Scrollback... and Export Visible Output..., Start Recording, Pin Selection (keeps the selected text pinned above the terminal until you unpin it), View as Diff, and in split view Split Right and Split Down.

Close confirmation

Closing a single terminal doesn't ask first: it ends whatever is running in it. Closing Termpolis while AI agents are running shows AI Agents Running, where you choose Cancel or Close Anyway.


6. Tab & Split Views

Tab view with multiple terminals

Terminals are shown in one of two view modes.

Tab view (default)

The active terminal fills the window. Switch terminals by clicking one in the sidebar's Terminals list, with Alt+1…Alt+9, or with Ctrl+Tab / Ctrl+Shift+Tab.

Split view

Split view

Every open terminal gets its own pane, sized evenly. Each pane's header has Split Right and Split Down, which open a new terminal with the same shell and folder beside or below it; the terminal's right-click menu has them too. Splits nest, so you can split a split. Drag a divider to resize. Click a pane to make it the active terminal, and use the close button in its header (or Ctrl+Shift+W) to close it.

Switching views

Click Split View / Tab View in the sidebar toolbar, press Ctrl+Shift+G, or run Toggle Split View from the command palette (Ctrl+K). Termpolis remembers the view when you restart it, and lays split panes out evenly again.


7. Settings

Settings panel

Open with the gear icon at the top of the sidebar, or choose Open Settings in the command palette (Ctrl+K). Ctrl+/ opens it straight on the Keybindings tab. Settings takes the place of the terminals in the main area until you close it, and the installed version and a Check for updates button sit at the top. Tabs across the top group the settings:

  • General — Safe Import (scan a third-party skill, plugin or MCP server before you install it), the default shell, terminal defaults (theme, font size, font family), naming agent terminals after their folder, the default model for each AI agent (Claude Code, OpenAI Codex, Antigravity CLI), whether terminal apps may capture the mouse, memory recall on agent launch, Import / Export Memory, and Privacy: crash reports and anonymous usage statistics are two separate switches, both off unless you turn them on, and a change applies at once (details in PRIVACY.md).
  • Memory & Learning — the memory dashboard: what is stored and where it came from, the knowledge graph, learning over time, self-competence by domain, receipts, and every recent memory operation. It also shows whether memory runs in its own process. If that process cannot start and your store is larger than 256 MB, Termpolis does not load the store on the main thread instead (that would freeze typing for seconds): memory is shown as unavailable and retried in the background, and memory searches and saves report an error until it is back (TERMPOLIS_MEMORY_INPROC_MAX_MB moves the limit, TERMPOLIS_MEMORY_INPROC_GUARD=off restores the old fallback). It can also store vectors as int8 to use 4× less RAM, and has the switches for Claude Code's saved notes: Include Claude Code's saved notes in memory (on by default, only once you have connected your agents), Let general notes help in other projects (off by default), what has been imported, and Remove imported notes (see Claude Code's saved notes). Under them an Activity record panel shows how much of the record of what memory did this computer holds and has a Clear activity record button (described under A local record of what memory did).
  • AI Security — Strict Mode, the Gemini account mode, what each agent does with your data, always-on prompt watching (it records a secret you send to an agent but never changes or holds back what you type), Commit Shield and its git hooks (block a commit or push that carries a secret), Egress Guard (flags agent traffic to unexpected hosts), memory scrub (redacts secrets before they are stored), the always-on background watchers, the cloud-bound audit log, and a manual pre-paste secret scan.
  • Voice — voice dictation: the on/off switch, your Groq API key, hold or tap activation, and auto-submit (see Voice Dictation).
  • Keybindings — rebind the core shortcuts, and add custom shortcuts that type a snippet into the active terminal (see §9).
  • Agent Ratings — score each agent from 1 to 5 in 10 categories; the swarm conductor uses the scores when it hands out work (see §10).
  • Shell Config — edit your shell profiles in a built-in editor: the PowerShell 7 and 5 profiles on Windows, plus .bashrc, .bash_profile and .zshrc.
  • Token Savings — Token Headroom, including the switch for the Claude Code compression proxy (see below).
  • Remote — let the Termpolis Remote phone app connect, set the relay address, and pair a phone (see §29).
  • Accounts — more than one sign-in per agent: each account's card, Add account, and how switching works (see Accounts).
  • Agent Integration — whether Claude Code, Codex and Gemini CLI are connected to Termpolis, every change Termpolis made to their settings, and Disconnect (see Connecting your coding agents).
  • MCP Servers — the MCP servers your agents reach through Termpolis, what each agent on this machine has configured for itself, and the gateway policy for a tool call that no rule covers.
  • Processes — find and end what is quietly slowing the machine down (see below).

Changes save immediately. There is no "apply" button, except in the Shell Config editor, which writes a profile only when you press Save.

Token Savings — the Claude Code compression proxy

By default, each Claude Code session Termpolis launches sends its API requests through a small compression proxy on your own machine (127.0.0.1), which forwards them to api.anthropic.com and nowhere else. It shrinks tool results — large file reads, command output, search results, MCP results — and pasted images, and the agent can call retrieve_full to get any compressed block back in full. It never rewrites a tool call's input: what Claude asked a tool to do — a Bash command, a file edit, a subagent prompt — is forwarded byte-for-byte, however old it is. Gemini CLI and your shells never go through the proxy, and Codex goes through it only to have each message's reasoning effort picked (see Model per message); nothing in a Codex request is compressed.

  • Turn it off with Route new Claude Code sessions through the local compression proxy in Settings → Token Savings (the tour's last step and the one-time privacy review offer the same switch). New sessions then talk to Anthropic directly; a session that is already running keeps its route until it ends.
  • Your own route wins. If ANTHROPIC_BASE_URL, HTTPS_PROXY, HTTP_PROXY or ALL_PROXY (upper- or lower-case) is set, or CLAUDE_CODE_USE_BEDROCK, CLAUDE_CODE_USE_VERTEX or CLAUDE_CODE_USE_FOUNDRY is on — in Termpolis's environment, or in the env block of Claude Code's own settings.json (~/.claude/settings.json, or the one in your CLAUDE_CONFIG_DIR) — the proxy steps aside. Settings → Token Savings names the variable it found; Claude Code keeps the route you configured, and nothing is compressed.
  • Age out old history (on by default) swaps old tool results in very long conversations for stubs that retrieve_full expands. It never ages out the result of a retrieve_full or memory call.
  • Shorten tool results: Late or Legacy. Since v1.55 the default, Late, leaves a tool result whole until it reaches 40,000 characters and shortens it from there, because a retrieval is a whole extra turn that re-reads the cached prefix and, on the author's machine, the model has recently asked for most of what is shortened early (about 69% on Opus 5.5 and 44% on Sonnet 5.5). Legacy shortens from the size of your tier, as v1.54 did. Choose in Settings → Token Savings → Shorten tool results; a change applies the next time Termpolis starts, because one proxy serves every Claude session of a run. Repeats still collapse, near-repeats are still sent as a patch, old history still ages out, and everything shortened is still one retrieve_full away, under both. The first start after upgrading to v1.55 rebuilds the prompt cache once for a conversation you resume, because Late leaves whole the results v1.54 had shortened.
  • A holdout on your own work. About one distinct opening message in ten, chosen by a hash of the conversation's first message and kept for as long as that message stays the same (a compaction summarizes the start of a conversation, so a compacted one can be assigned again), always runs Legacy whichever setting is on, so the Elision health card, in the receipt section near the top of the tab, above the proxy settings, can compare the two on your work. A first message that is exactly the same, character for character, always lands in the same group. Claude Code puts more than your words in that message (your project instructions, the date, the state of your repository), so the same words typed in another session often start a new draw. The card says "not enough data yet" until each side has 500 requests from at least five groups of conversations, reads the cost gap at 99% confidence, and shows the share of requests and of billed units that went on retrieval turns, which settles first. The margin is read from a wider interval than a plain 99% one, because the holdout is a tenth of the conversations and its costs lean one way: on simulated arms that were the same it named a difference falsely in under 1 read in 100 in each direction, where a plain 99% interval did so in about 1 to 3 reads in 100 when it said Late cost more. The price is more data to name a real difference: about 3.5 times as much when the side with fewer groups has five, falling to about 1.8 times as much at twenty. It sees cost and round trips, not the quality of an answer.
  • Elision health. For each kind of tool result, how many were shortened, how many the model later asked back for, the break-even rate and a verdict, judged for the setting in force (under Late, the results of 40,000 characters and over; the conversations kept on the old behavior are shown beside it and labeled as not what your setting does). It also counts the turns that only fetched shortened text, including the turn that first had to load retrieve_full through ToolSearch, and the aged-out stubs found in conversations too short to have made them. A ToolSearch turn is paired with the retrieve_full turn that follows it by the conversation's opening message, so when several conversations that start with the same message (parallel subagents, say) are interleaved, a ToolSearch turn can be left uncharged and the retrieval-turn figure is a lower bound there.
  • What it reads of the answer. The proxy also reads each answer as it streams back, for Anthropic's token counts and the names of the tools the model called, for whether the answer finished (a finished answer ends with a message_stop event, one the API broke off carries an error event), and for a ToolSearch call whether its query asks for retrieve_full. That is how it counts the turns that only fetched shortened text and the ToolSearch calls that led to them, and counts none for an answer that did not finish, since the client asks again. It keeps counts only, and forwards the answer to Claude Code unchanged.
  • The headline. Settings → Token Savings leads with billed units avoided, net of the turns that fetched elided text and of text asked back. Retrieval turns are counted from v1.55 on, so the all-time figure is higher than your bill supports, and stays higher: it carries the older history as it was, never netted, and only what is added from v1.55 on is. The pane says so with the share of responses read; this session's figure is fully netted. It is an estimate: the short view that stays behind in a conversation is not charged.
  • The full text of every compressed block stays on your machine, unencrypted, in headroom/ccr in your data directory (capped at 200 MB). Beside each one the app keeps the tool's name, the model's name, how it was shortened (a window of it, a reference to a repeat, a patch against a near-repeat, or aged out of a long conversation), the two sizes and which setting the request ran under, and counts by kind and size of how many results were shortened and asked back (headroom/elision-ledger.json), with cost counters for the two groups of conversations (inside headroom/proxy-totals.json). Counts, sizes and names only; see Privacy.

Spending fewer tokens per task (v1.55)

  • The levers on the receipt. Settings → Token Savings lists one row per lever: Routing (the share of requests each Claude model family served), Late elision, Relay, Focused recall, Graph first and Lean output. A row says measured against the holdout only once its comparison against a randomised holdout clears a 99% interval; before that it says collecting (n of N), where n is the thinner arm's count and N the count a verdict needs. A row with no holdout behind it, or whose per-arm cost this release does not record, says observed and gives a count, never a saving. In 1.55.0 only Late elision has a comparison that can reach measured; Relay and Focused recall count prompts and sessions since launch.
  • Re-route chip. After a session's first prompt, if a different model fits the task better than the one the session started on, the agent's header offers it once. Switching restarts the agent on that model and resumes the conversation; ignoring it changes nothing.
  • Fresh session (the relay). When Claude Code compacts a session Termpolis launched, the memory hook asks the summary to keep the decisions made and why, the file paths touched, the open next step and failing test names, sends the last prompt to Termpolis on this computer, and the header offers Fresh session, which starts a new session and carries the work over through a memory handoff note. A new session re-reads a short prefix instead of a long one.
  • Part of a result. retrieve_full takes lines (a range such as 120-180) or grep (text, or /a|b/ for any of several; never a regex) and returns only those lines of a shortened result, so an agent need not bring the whole thing back.
  • Outline before a whole read. When Claude Code is about to read the whole of a file over 1,500 lines, the memory hook asks Termpolis for the file's outline (its symbols, from the code graph, at most 60 lines) and hands it to the agent first, so it can read just the part it needs. Only the file's path is sent, and only to Termpolis on this computer.
  • Review loops. The swarm conductor's prompt asks review loops to use Sonnet reviewers at medium effort, a Sonnet fixer at high effort, at most 3 rounds, each round reviewing only the changes since the last, stopping once a round finds no blocker or major issue, and to run tests, lint and typecheck as commands rather than ask a model.
  • Lean output. The compression proxy's output steering asks for a diff or the changed lines rather than a whole file back, and in the balanced tier for a closing summary of at most 5 lines.

Long conversations — a smaller Claude Code context window (opt-in)

Claude Code compacts a long conversation into a summary when it nears the model's context window, and every request until then re-reads the whole history. Settings → Token Savings → Long conversations lets you pick a smaller window (500,000, 400,000, 300,000 or 200,000 tokens), so it compacts sooner and later requests carry less. It is off by default: it changes how much of a long conversation the model keeps word for word, and Termpolis cannot measure what that does to the quality of answers on your work.

  • What it sets. Claude Code's own CLAUDE_CODE_AUTO_COMPACT_WINDOW variable, in the environment of the terminals Termpolis opens for Claude Code (a launch, a restored or resumed session) and of every terminal created through Termpolis's MCP create_terminal tool: a swarm worker's, one an agent opens, and the shell a paired phone opens. The command is not known when such a terminal is created, so all of them get it, as they get the proxy route; Codex and Gemini CLI never read it. Not the other shells you open yourself. It reaches new terminals only; one that is already open keeps what it started with. It needs neither the compression proxy nor the tool-output compression, so it works with the proxy off, stepping aside or not running.
  • What it never overrides. A window you already set: CLAUDE_CODE_AUTO_COMPACT_WINDOW in the environment Termpolis runs in, the same variable in the env block of your Claude Code settings, or a window saved with /autocompact or autoCompactWindow (Claude Code lets the variable win over those, so setting it would silently undo your choice). A window you export in your shell's own profile is applied after the terminal starts, so it wins too. To tell, Termpolis reads, while this is on and when it opens a Claude terminal, the env block and the window keys of three files: settings.json in your Claude Code config folder (~/.claude, or the one CLAUDE_CONFIG_DIR names) and .claude/settings.json and .claude/settings.local.json in the folder the terminal opens in (the home folder, if the folder it was asked to open in has been deleted). Of the env block only the window variable's value is kept, nothing else in those files is kept, nothing is written, a file that is missing or is not valid JSON counts as not set (as it does for Claude Code), and a file that is there but cannot be read for a moment (another program has it locked) is tried once more and, if it still cannot be read, counts as set, because Termpolis cannot tell, and so does a file larger than 1 MiB, which it does not read. See Privacy.
  • What Claude Code does with it. Anthropic documents the window as 100,000 to 1,000,000 tokens, and Claude Code never uses a window larger than the model's own, so on a model whose window is already this size or smaller the setting changes nothing. Anthropic does not say how close to the window a conversation compacts.
  • The saving is modeled, not measured. The figures beside each window come from replaying one person's October history: about 10 to 13% of the bill at 400,000, 16 to 21% at 300,000 and 20 to 29% at 200,000, with 15,000 to 80,000 tokens lost to each compaction. The 500,000 figure (about 5 to 7%) is estimated between two modeled windows. Late and a smaller window overlap, so together they save less than the two figures added. They are a model, not a promise for your work.
  • Places it cannot see. Claude Code lets this variable win over a saved autoCompactWindow wherever that lives, and over a window set in a subagent's definition. Termpolis does not read a subagent's definition, a --settings argument or your organization's managed settings, including the managed-settings file. An env entry that sets the variable itself in one of those places is different: Claude Code writes it over the value Termpolis passed, so that one wins over this setting. Turn the choice Off first if you rely on any of them.
  • Subagents compact at it too. Anthropic's page on subagents says that when this variable is set, a subagent uses its window instead of the one its own definition sets. So Claude Code's subagents use the variable's window too, so they compact sooner as well.
  • Typing /autocompact in a session. While the window is on, /autocompact typed in a session is reported as overridden and not saved (Anthropic's model-config page says so). Turn the window Off first to set one that way.
  • A conversation already longer than the window. Anthropic does not say what a resumed or restored conversation that is already longer than the window does, so Termpolis cannot tell you. The Activity Feed's compaction event shows when it happened.
  • A percentage you set. If you also set CLAUDE_AUTOCOMPACT_PCT_OVERRIDE, Anthropic defines it as a percentage of the auto-compact window, so with a smaller window it applies to that window: 50% of a 300,000 window is 150,000, and compaction comes sooner than you chose. Termpolis does not treat it as a window of your own.
  • Four limits. Project settings are read from the folder the terminal opens in; on macOS and Linux, Claude Code started in a subfolder of a git repository keeps its local settings file at the repository's root, which is not read. Your user settings are read from the config folder Termpolis itself runs with, so a CLAUDE_CONFIG_DIR that a script sets inside the terminal (to switch accounts, say) names a folder that is not the one checked. A --autocompact flag typed on a command line cannot be seen when the terminal opens, and Claude Code lets the variable win over the flag, so to use the flag turn the window Off first. And a plain shell terminal in which you type claude by hand is not a Claude launch Termpolis knows about, so it gets neither this nor the proxy route; nor does a Claude session that the Workflow Orchestrator's agent steps start themselves.
  • Checking where it compacts. The Activity Feed's compaction event shows the size of the conversation when Claude Code compacted and whether it chose to (for example context compacted at 246,000 tokens (auto)), read from the same transcript line the feed already reads and nothing else on it. With a smaller window chosen, that is where to look to see that it compacts about where you set it; Anthropic does not say how close to the window it does.
  • Meters. Context meters, in Claude Code's status line and in Termpolis, keep measuring against the model's full window (Anthropic says so of the status line), so they do not show how near the smaller window is.

Processes (stuck-process cleanup)

Headless AI runs, hooks and crashed sessions can leave processes behind that hold memory, CPU and file handles long after anyone needs them. The Processes tab lists them in three groups:

  • Headless AI agents — Claude Code (-p, --print, stream-json, mcp serve), Codex (exec, app-server, mcp-server) and Gemini CLI (-p, --acp) running non-interactively, including ones started through node, npx or python — by a script, hook, scheduled job or swarm worker, by an editor or app that drives it and may be using it right now (an IDE extension, an MCP or ACP client), or by hand in a terminal. Interactive sessions in a terminal are not listed.
  • Git — git that is frozen (Windows), lost the program that started it, or has run for more than 30 minutes. Git daemons (fsmonitor, credential cache, cat-file --batch) are not listed, and neither is git someone is using: git gui, gitk, git difftool or git mergetool, or git waiting on a common pager or editor.
  • Leftover shells & tools — shells, cmd /c / PowerShell wrappers, MCP servers, console hosts and tools whose parent is gone, or (on Windows) that are frozen. A wrapper is listed only while it still runs something. On macOS and Linux, where closing a terminal or a GUI launcher orphans everything it started, an orphaned shell, wrapper or tool is listed only while its tree still holds git, a headless agent or an MCP server.

Each row shows who started it (Termpolis, another program, or parent exited), age, CPU time, memory, any child processes, and the command line. For a shell left by an agent's Bash tool, the command that tool actually ran is shown instead of the harness around it. Common secret formats (tokens, passwords, URL credentials, auth headers and cookies) are masked, best effort, and your home folder is shortened to ~.

A STUCK badge marks what is usually safe to end: something that serves no TCP port, has nothing interactive open in it (see below), and is either frozen on Windows (every thread suspended, in a process over a minute old — Git Bash can leave a git or jq frozen for good when the script that started it exits at the wrong moment) or orphaned: the program that started it has exited. Only a process's own state counts: a frozen process inside a healthy tree gets a row of its own, so it never marks the tree around it stuck. On macOS and Linux a stopped git is listed after 30 minutes but not marked stuck while the shell that stopped it is still running, since it is usually a job paused with Ctrl+Z. An orphaned headless agent is only marked stuck once it has been running for an hour, since it may be a deliberate nohup job — a scan keeps no history, so how long ago it lost its parent is not known. An orphaned git is only marked stuck once it has run for 30 minutes, since it may be a long clone, and never for being orphaned when it is git's own gc or maintenance, which detaches on purpose and ends by itself. Any other orphan (a shell, wrapper, tool or MCP server with no git or headless agent in its tree) counts as soon as it is listed. An orphan is listed only once it is 5 minutes old, except a headless agent (always listed) and, on Windows, a frozen process (listed once it is a minute old). Stuck is judged from process state alone, so a download or script you detached on purpose looks the same — check the command before you kill.

Some processes are never taken for orphans. On macOS a running launchd job (a LaunchAgent) was started by launchd, not adopted by it. On Linux everything inside a systemd service counts as that service's job, and a process stays in its service when its parent dies, so the service's own processes and anything they leave running (on Debian and Ubuntu, whatever a cron job started) are never taken for orphans and never marked stuck. On Windows, a Git Bash program whose Windows parent has exited (Git Bash starts each stage of a pipeline that way) is traced to its real parent with that Git install's own ps, so a pipeline that is still running is never taken for an orphan; when that cannot be confirmed, the program is not marked orphaned and a note says so.

Anything listening on a TCP port is never marked stuck — it may be a server you started on purpose — but only TCP counts: a server on UDP, a named pipe, a Unix socket or stdio can still be marked stuck. If the port table cannot be read in full (netstat on Windows, lsof on macOS or ss on Linux failed, is missing or timed out), nothing is marked stuck and a note says so. A process tree that holds something a person is using — an agent CLI open in a terminal, a git GUI or merge tool (git gui, gitk, git difftool, git mergetool), or a common pager or editor — is never marked stuck, and is not listed for being orphaned or for being long-running git. A headless agent is the exception to the second part: every headless agent is listed, so one that holds something in use still appears, and can still show parent exited and the orphaned or long-running reasons — it is just never marked stuck.

Kill selected and Kill all stuck end the whole process tree after an inline confirmation. Before anything is signalled, each target is checked again against a fresh scan by pid and start time, and skipped (and reported) if it has exited, is still running but no longer listed, or its pid now belongs to a different process; Kill all stuck also skips anything that is no longer stuck. Anything not skipped is ended together with the child processes that the fresh scan finds under it. On macOS and Linux a kill is SIGTERM, then SIGKILL after 3 seconds. A git killed mid-write can leave a stale .git/index.lock — delete it if the next git command complains.

Scanning happens only when you press Scan or Refresh — never when the tab opens, and never on a timer. A scan reads every process's command line and the TCP port table, which endpoint-security software logs as discovery (v1.54.3). Termpolis itself, the programs that launched it, and its own windows and terminal shells are never listed. Processes in other Windows sessions (other users' processes on macOS and Linux) are not listed; elevated ones can only be killed from an elevated Termpolis.


Voice Dictation

Talk instead of type. Transcription uses Groq's cloud Whisper API — your recorded audio is sent to Groq for transcription. It is off by default and opt-in; turn it on in Settings → Voice and connect a Groq API key.

Engine. Transcription runs on Groq's hosted Whisper API (whisper-large-v3-turbo by default; whisper-large-v3 is selectable for maximum accuracy). The call happens in Termpolis's main process, so your API key never enters the renderer — it is validated when you connect and stored encrypted in your OS keychain (Windows DPAPI / macOS Keychain / Linux libsecret), never in settings or logs. Groq is fast (~200–300 ms for a short clip) and cheap: the free tier covers everyday dictation, and paid is about $0.04 per hour of audio.

How to use it.

  • Hold Ctrl+Shift+L and speak; release to send (true push-to-talk). Prefer hands-free? Switch to tap-to-start / tap-to-stop under Activation. The hotkey is rebindable.
  • Wait for the "Listening…" badge, then speak normally. Level doesn't matter much — the model handles quiet and loud speech — but it does need to actually hear words.
  • In an AI-agent terminal (Claude · Codex · Gemini) your words are sent straight to the agent as a prompt — the agent absorbs minor mis-hearings, so just talk naturally. (Optionally auto-submit so the prompt sends the moment you finish.)
  • In a plain shell the transcript is inserted but never run automatically — you review it and press Enter yourself, so a mis-heard command is never executed for you.
  • When dictation ends the caret returns to the terminal so you can keep typing or dictate again without clicking back in.

"No speech detected." If the microphone captured silence or no clear speech, Termpolis shows a brief "No speech detected" notice and does nothing — it will never inject a guessed phrase when it didn't actually hear you. (This is what eliminates the old "I'm sorry, what is that?" phantom transcripts: on no-speech audio a speech model invents filler, so Termpolis gates that out at the source — and silence is never sent to Groq, since the speech/noise gate runs locally first.) Just hold the key, speak, and release again. To improve capture, speak after the badge appears and reduce background noise; the model itself is robust to volume.

Privacy & reliability. Only the few seconds you actually dictate are sent, and only to Groq — never your files, terminal output, or other context. By default Groq does not train on or retain API audio; for the hardened setup, enable Zero Data Retention in your Groq console (the Connect dialog links you straight there). The transcript Groq returns is re-scanned by the secret scanner before it's injected, the same as anything else you send to an agent. If transcription fails, the red error bar tells you what went wrong — usually a missing/invalid key (Settings → Voice) or no internet connection.

Accounts

More than one sign-in for an agent: a personal and a work Claude Code account, two ChatGPT plans for Codex, two Google accounts for Gemini CLI. Each Claude Code or Codex account is a profile folder of its own (a new ~/.claude-<name> or ~/.codex-<name>, or a folder you already have) that signs in there by itself; Termpolis never reads, copies or keeps a sign-in. A Gemini CLI (agy) account is a name and the Google email it signs in with, because agy keeps one Google sign-in for your whole computer user. With no account added, nothing of this shows anywhere: no chip, no menu, no card, no banner, and every agent starts exactly as before.

Settings ▸ Accounts

One section per agent, a card per account: its colour, its name (click it to rename it in place; Enter keeps it, Esc doesn't), the email and plan its agent reports, its status (signed in, signed out, sign-in expired, out of usage until …, or signed in to another email than its own), whether new terminals start on it (Make it the default for new terminals), and the terminals on it now (click one to go to it). On the card: Sign in / Sign in again, Check (asks the agent who is signed in; it and Sign in look for the CLI on the same PATH a terminal would give it, so an agent installed under nvm, volta or ~/.local/bin is found even when Termpolis was started from the Dock or Finder), Connect / Disconnect Termpolis's tools and memory for that profile, Mark available after a usage limit, Re-copy from main profile, Offer automatically, the order (↑ ↓, the order the usage-limit card offers them in), and Remove…. Its folder, what it shares with your main profile and the email it should sign in with are under Advanced. Adding an account on a profile folder of its own lists your main profile beside it, as main.

Check doesn't ask while a Termpolis terminal runs on that account, except right after a sign-in: a check could refresh the sign-in that terminal uses, and a sign-in refreshed elsewhere can sign that session out.

+ Add account opens a short wizard: 1, a name and a colour (and the email, whether it is offered automatically, and whether to connect it, with what that writes; its folder and what it shares are under Advanced: a new ~/.claude-<name> by default); 2, sign in, right there (or Skip for now); 3, done, with the email it signed in as. A Gemini CLI account has no folder and nothing to connect: a name, a colour and its Google email. If ~/.claude/accounts.sh lists accounts, a banner offers to Import them. Termpolis reads that file and never changes it: when it exists, the wizard's last step shows the lines that would add a new Claude Code account to it, with Copy, since until it lists the account the scripts that read it count that profile as your default account.

The tab also says when something works against your accounts: a StopFailure hook in your Claude Code settings (see Your StopFailure hook), a variable or setting that outranks every profile's own sign-in (such as ANTHROPIC_API_KEY, an apiKeyHelper, or a switch to Bedrock, Vertex or Foundry; Termpolis names it and never shows its value), or an accounts.sh that lists other accounts than Termpolis has.

The account menu

In an agent terminal whose agent has two accounts or more, the header shows an account chip. Click it, press Ctrl+Shift+N, run Switch account… from the command palette, or right-click the terminal: a menu lists that agent's accounts, the current one checked, each with its email and status. Arrow keys, Home/End and typing a name's first letters move in it; Enter picks; Esc and Tab give the keyboard back to the terminal, from the list, from a question and from the submenu alike. While this terminal's switch runs, the other rows are off and say "Switching to codedev…", so a pick is never dropped without a word. Below them: New terminal on ▸ (the same agent in the same folder, on another account, in a fresh session: never the conversation this terminal holds, so no two agents write one transcript; the --model and other options the agent was launched with come with it, but not a model you picked later from the header's Model menu, and its --resume, --continue or --session-id do not), Add account… and Manage accounts…. The chip turns red when the profile is signed in to another email than the account's own, when the account was removed (removed account), or when the terminal's account was changed by hand (?, after you set CLAUDE_CONFIG_DIR or CODEX_HOME there yourself, or move the terminal with a script of your own such as switch-now, claude-switch or /switch-account: Termpolis can't see which profile the restart then runs on, so a conversation that may be on an account kept from the automatic offers asks for your confirmation before it moves; Termpolis also notices it from the line claude-run.sh prints as it restarts Claude on another profile, and from a /switch-account that Claude Code's own completion typed for you, /sw and Tab); switching from the menu puts it right. The conductor's terminal has no chip: it is never switched.

Switching in place

Pick an account and the terminal switches there, keeping its conversation: Termpolis ends the agent the agent's own way, starts it on the account, and resumes the conversation. An overlay on the pane shows the two accounts and the three steps (Ending this session, Starting Claude Code on codedev, Resuming your conversation); keys you type meanwhile are held, and it says so, and nothing else can type into the terminal until it is done: an MCP tool's or the phone's writes are refused. When it is done, a toast says so ("Claude Code is on codedev now. Conversation resumed.") with Switch back for 10 seconds (longer for a longer message, up to a minute, so a warning in it can be read). If the account can't see the conversation, Termpolis says so: for Claude Code the card offers Start a new session on ; Codex and Gemini CLI start one by themselves, and the toast says where the old conversation still is (Past AI Sessions).

  • An agent at work is never interrupted: its row says Switch when it finishes, the chip shows the waiting switch with a way to cancel it, and it runs when the turn ends.
  • A signed-out account says Sign in to and switch: the sign-in sheet opens, and the switch goes on by itself once you are signed in.
  • An account not offered automatically (a work account) asks once more, onto it and off it, wherever the switch was asked for: the account menu, the limit card (also before its "Sign in and switch"), a queued switch, Try again and a swarm worker's restart all carry your yes, and the switch itself is refused without it. Carrying a conversation off it offers a new terminal there instead. Switch back on the toast is offered only between accounts that are offered automatically; to or from the work account, use the menu, which asks. It is the login in a profile that counts, as in your own accounts.sh: a profile that is signed in to the work account's email is the work account for all of this, whatever folder it sits in and whatever the registry calls it (your logins can move between folders). An account's card in Settings ▸ Accounts ("signed in to … instead!") and its row in the menu say so when its profile holds another email than its own.
  • When a switch can't finish, the overlay becomes a card that says what happened and what the terminal shows now, in a sentence each, with what you can do next: Try again (first where the card asks whether the agent exited, so a stray Enter asserts nothing), It has exited, switch now, Start a new session on , New terminal on , Sign in to , Sign in to and continue the conversation in a new terminal, and Show steps. A switch that put the line in but whose agent never came up (nothing showed, a question on screen) can be tried again; one that finished is still refused as "already runs on". If the account's stored sign-in had expired, the terminal sits at the CLI's sign-in screen holding no conversation: signing in from the card opens a new terminal beside it that resumes the conversation by its id with the flags the terminal was started with. Termpolis never types anything it can't see the agent is ready for.

What each agent does:

  • Claude Code is ended with its own /exit, typed into its input and entered only once the input shows it, and resumed with claude --resume <id>: the id it printed as it exited (Claude Code prints the flags a session was started with in that hint, claude --effort max --resume <id>, and Termpolis reads the id out of them), or the one Termpolis started the session with. The flags the terminal was started with come back with it, word for word: its --model in any spelling (opus[1m] too), --dangerously-skip-permissions, a permission mode, denied tools, an added folder, Codex's approval and sandbox. A line Termpolis cannot repeat faithfully (a prompt in it, a quote, an operator, a flag with several values, an odd model name) is refused before anything is typed, with a card that offers a new terminal instead, because a relaunch that dropped one of those could drop a restriction. A command that only starts with an agent's name (claude-sandbox) is not that agent: it has no account, chip or switch, and launches as it always did. Where Termpolis did not see how the agent was started (you started it by recalling an earlier line from the shell's history or with a reverse search), or saw a line the shell edited as you typed it (Tab completion), the relaunch is bare, and the toast says so: "Its model and options were not carried: Termpolis didn't see how Claude Code was started." That holds as well when you recall a line (Up and Enter, Ctrl+P) after an agent Termpolis launched has exited: Termpolis then forgets the conversation that launch named, and a switch before the new session has a turn starts a new session instead of resuming the old one. A Claude Code session in a worktree that exited with no conversation to name (a session with no turns prints none) comes back in the main folder, and the toast says which worktree it did not go back into. When the other account's projects folder isn't shared with this one's, Termpolis first copies that one conversation's transcript there.
  • Codex is ended with its own /quit, and resumed with codex <its flags> resume <id>. A new Codex account shares your main profile's sessions folder, so the other account finds the conversation. When it can't ("No saved session found"), Termpolis starts a new Codex session in the same terminal on the other account, and says so.
  • Gemini CLI moves every agy terminal at once (below).

Models. A new terminal on an account starts on the agent's Default Model (Settings → General → Default Models) like any other new terminal. A terminal you switch comes back on the model it was last started with, one you picked from its Model… menu included, and changing its model later leaves its account alone (see Choosing the model). The Model… menu and a switch never run together: one asked for while the other is running is refused, and the notice or card says why ("an account switch or another model change is running in this terminal", or "a model change or another switch is already running here").

When you switch accounts, Termpolis never resumes with --continue or resume --last: in a folder where several agents work, those pick up another agent's conversation. (The model menu's fallback to the most recent conversation is the model menu's own.) If an agent has already exited when you switch, Termpolis sends it no key at all and goes straight to the relaunch; if it can't tell whether the agent is still running, it types nothing and asks. Where the operating system can say what runs in the terminal (the read the Model… menu and write_to_terminal's submit use too), it has the last word: an agent it says is gone gets no exit key, and Termpolis types the account's line only once nothing but the shell is left, which can take several seconds for an agent that runs shutdown hooks. Where it can't say, the screen's answer stands.

The account stays in the terminal's shell. Termpolis puts the account on the command it types, in that shell's own words: export CLAUDE_CONFIG_DIR='…' && claude … in bash, zsh and Git Bash, & { $env:CLAUDE_CONFIG_DIR = '…'; claude … } in PowerShell, and set "CLAUDE_CONFIG_DIR=…" && call claude … in cmd (Codex's is CODEX_HOME; for your main profile the variable is unset instead). The setting lasts as long as that shell, so an agent you start there later, by hand or from the model picker, runs on the same account. Each form runs nothing at all in another kind of shell, so a shell started inside the terminal's own makes the line fail instead of starting the agent on the old account. Once an agent has two accounts or more, every launch Termpolis types for it names its account this way, so a CLAUDE_CONFIG_DIR your shell profile sets can't move it; with no account, or only your main profile, Termpolis types the bare command, as before. agy has no such variable, so its launch is always the bare agy.

Signing in

The sign-in sheet ("Sign in to codedev · Claude Code") asks how first: for Claude Code, Claude subscription, Anthropic Console or SSO; for Codex, ChatGPT account or Sign in with a code (a one-time code shown large, with Copy, and the address to enter it at). Termpolis then runs the agent's own sign-in in a hidden session of its own, never a terminal, and the agent opens your browser. The sheet shows the address it printed, with Copy and Open again, and a field when the agent asks for a code from the page ("If the page shows a code, paste it here"). When it is done it says Signed in as , as the agent's own status command reports it, and warns if that is another account's email, the work account's, or not the account's own. Signing in again in a profile also signs out the other sessions on it, outside Termpolis too: the sheet says so first. An error is said in plain words, with Show details (the agent's own words, masked); after 10 minutes with no sign-in it stops, with Try again. Cancel, or closing the sheet, ends the session. See PRIVACY.md for what Termpolis runs, keeps and never keeps.

Gemini CLI: one sign-in for every agy terminal

agy keeps one Google sign-in for your computer user, in the OS keyring, so it can't run on two Google accounts side by side: signing it in as one account signs every agy on this computer in as that account. So picking another agy account moves every agy terminal you opened: a confirmation says so ("Switching to moves all 3 Gemini terminals; each one resumes its conversation") and names any that is working (Termpolis waits for its turn to end). Then, in the sheet: each agy terminal is ended the switch's way (with its own /exit, and never Ctrl+C, which kills agy), agy is signed out and in again in Termpolis's own hidden agy (its sign-in choices as buttons, the browser step, the code field), and each terminal resumes its conversation with agy --conversation=<id>, the id it printed as it exited, typed the same careful way (the first part of the line first, so a draft in an agy you started again by hand meanwhile is never submitted). A conversation agy can't find there starts anew. Stop it before the sign-out and every terminal it ended goes back to its conversation on the sign-in it had; if the sign-in doesn't finish once agy is signed out, the sheet offers Try the sign-in again or Resume them now. Esc or the close button there does what Cancel does mid sign-in (the same two choices) and, once terminals were ended, resumes them before it lets go, so no Gemini terminal is left at a bare prompt; Stop is honoured up to the sign-out, even when pressed during the last terminal's own exit. The confirmation says that an agy running anywhere else on the computer (a swarm worker, another window) moves to the new sign-in too, and that Termpolis does not resume it. A Gemini terminal where agy was not running (you had quit it by hand) is left as it is, marked "agy wasn't running here: left as it is", and is not started again after the sign-in changes; it does not count among the conversations resumed. If GEMINI_API_KEY or agy's ADC sign-in is set, the sheet says so before anything is typed. Termpolis reads agy's email only from what agy itself shows on screen.

When an agent runs out of usage

An admin or workspace stop on a seat counts as running out too, since another account may have a seat: Claude Code's "Your seat type doesn't include usage", "Your usage allocation has been disabled by your admin" and "Your group's usage limit is set to $0", and Codex's "Your workspace is out of credits" and workspace spend cap (their wording is read from the programs' text; no capture of them drawn exists). A card is never opened for an old limit the CLI redraws after a switch or a resume: a pasted block of lines and Shift+Enter are text in the box, not a prompt sent; /resume and /continue start over like a relaunch; and a limit counts only if its words were printed after the turn began. A limit recorded before an agent started on that account, whose agent then answers a prompt, is lifted. One-line banners are one per agent and account, however many terminals ran out, and go when the agent is working again.

A card at the top of the stopped terminal's pane says what ran out, and the agent's own words under it ("Claude Code is out of usage on personal (a@example.com) until 4:00 pm."), with Continue on (the switch above, in one click), Other account ▾ (the rest, a sign-in first where one is needed, a new terminal, Add an account, or another installed agent in a new terminal) and Not now. The card never takes the keyboard. Only accounts offered automatically are offered, never one out of usage or one signed in to the same email as the account that ran out, and each is asked as the card opens whether it is signed in now (a signed-out one offers a sign-in first); a work terminal's first choice is a fresh session on another account; a limit on one model offers that model's way out first ("Switch this terminal to Sonnet"). Several terminals stopped on one account each show the card, with how many more ran out. A card sits under the pane's own toolbar, and goes by itself when its terminal has moved to another account (from the chip's menu, from Settings, from the toast's Switch back). A terminal out of view gets a banner that takes you to it, and Termpolis tells the OS when its window isn't in front. An agent with one account gets a one-line banner instead ("Claude Code is out of usage until 4:00 pm", with Add an account and Don't show again). A menu Codex holds a turn on, such as its offer of a cheaper model, is yours to answer: it gets a banner, never the card. Untick Offer another account by itself when an agent runs out of usage and a banner, Choose an account, stands in for the card.

Termpolis watches the agent terminals it starts, and only while their agent runs: a limit an agent drew before it exited, or one cat prints from a file, opens nothing, and nor does the old limit a resumed conversation shows again before it has taken a turn. Those terminals carry TERMPOLIS_LIMIT_CHOOSER=1 in their environment once an agent has two accounts or more; a plain terminal where you start an agent yourself isn't watched, and doesn't carry it. A terminal the swarm tools create does carry it (it is set when the terminal starts, before anyone knows what will run in it), once any agent has two accounts: in one that never hosts a watched agent it only tells your failover hook to stand down, and a worker still gets the conductor's report as before.

For a swarm, a worker restart is offered only when a signed-in account can take the worker over now. A signed-out account is listed on that card beside it as Sign in to and restart: the sign-in sheet opens, the conductor's five minutes wait while you sign in (they run again if you close the sheet without signing in), and the restart goes on when you are signed in. With only a signed-out account nothing is offered and the conductor hears v1.54.0's report at the stop: a restart that waits on a sign-in is not one Termpolis can promise, so it holds nobody. The sign-in stays on the worker's chip menu and in Settings. The offer to restart its stopped workers on another account ("3 of the swarm's workers ran out of usage on personal" [Restart all on codedev]) shows in the same card style, inside the swarm dashboard and on each worker's pane: see When a worker hits a usage limit.

What carries over between accounts

  • Termpolis's memory is one store for every account. Each connected account reaches it, so what you work on in one is recalled in the others.
  • Learning reads the conversations of every Claude Code and Codex account you registered, whether its folder is shared with your main profile or not. The live views (Trace, efficiency) follow the main profile's folders, so they follow a terminal on another account only when its projects (Codex: sessions) folder is shared, as it is by default.
  • Conversations continue on another account through the shared projects and sessions folders, or, for Claude Code, through the copy Termpolis makes at the switch.
  • Not the prompt cache. The provider keeps it per account, so the first answer after a switch reads the whole conversation again, as a fresh session would.
  • Not the usage limit, which is the point of switching.

Your StopFailure hook

Claude Code runs a StopFailure hook when a turn stops on a usage limit, an expired sign-in or a billing error. If one in your settings opens a window or switches accounts by itself, it acts at the same time as Termpolis's card, and two agents can end up on one conversation. Settings ▸ Accounts says so when it finds one, with the check to add to the hook (skip when TERMPOLIS_LIMIT_CHOOSER is 1) and Copy the check. A hook that skips there skips an expired sign-in too, inside those terminals: sign the account in again from its card or its menu.

Launching on an account that is out of usage

A fresh launch that falls on the agent's preferred account (the sidebar's agent button, the Welcome screen, the command palette) asks first when that account is out of usage and another is free: "Claude Code: personal is out of usage until 4:00 pm. Launch on codedev?", in a sheet, before any terminal is made. Launch on the first free account, on another free one, on the limited one anyway, or Cancel (no terminal). Only accounts that are a way out are offered (offered automatically, not out of usage, not the same sign-in, not known to be signed out), never the work account. Nothing is asked for an account you picked (Launch on from the sidebar's list, which opens from the button beside the agent or with a right-click, and marks accounts that are out of usage), for a limit that lifted, a model's limit, or when no other account is free. Restoring a workspace goes on the account each terminal was on; resuming a past session goes on the agent's preferred account, whichever account wrote it.

Removing an account

Remove… forgets the account, and takes Termpolis's entries out of its profile folder. (Your main profile has none of those: Agent Integration connects it, and stays on; removing the main account only makes Termpolis forget it.) Terminals running on it keep running; their chip says removed account, and Termpolis answers nothing in that profile any more, folder-trust dialogs included. Termpolis never deletes a profile folder: the sign-in is in it, and some of its subfolders are links into your main profile, so deleting the folder with a tool that follows links would delete your main profile's conversations too.

To delete a folder Termpolis made yourself:

  1. Sign the account out with its own agent first, if you want the sign-in gone too.
  2. Remove each link in it first, as a link: in cmd, rmdir "<folder>\projects", then the same for commands, skills and agents (Codex: sessions). rmdir removes a link without opening it. On macOS and Linux, rm "<folder>/projects" with no trailing slash (a slash would open the link).
  3. Then delete the folder, in File Explorer or Finder, or with rmdir /s in cmd.

Never use PowerShell 5.1's Remove-Item -Recurse on it while its links are there: it follows them and deletes your main profile's conversations, commands and skills.


8. Themes

Themes picker

Termpolis ships seven terminal themes: Dark (default), Light, Solarized Dark, Solarized Light, Monokai, Dracula and Nord. A theme sets the terminal's background, foreground, cursor, selection and 16-color ANSI palette; the app chrome around the terminals keeps its own colors.

  • Default for new terminals: Settings → General → Terminal Defaults.
  • One terminal: pick a theme in the New Terminal dialog, or change it later from that terminal's edit menu (right-click the terminal in the sidebar).

9. Keybindings

Keybindings settings

Settings → Keybindings lists the core actions and their shortcuts: copy and paste, history search, find in terminal, new, close, next and previous terminal, the sidebar, split view, the app log, clearing the terminal, and launching agents 1–3. To rebind one:

  1. Click its shortcut.
  2. Press the new combo. Press Escape, or click anywhere outside, to cancel.
  3. The new binding takes effect at once. If another shortcut already uses that combo, the row says Conflicts with ….

Each row's reset button restores its default, and Reset All restores them all. The three copy shortcuts (Ctrl+Shift+C, Ctrl+Shift+K, Ctrl+Shift+Q) are reserved and cannot be changed. The panel shortcuts, such as Ctrl+K for the command palette and Ctrl+Shift+S for the swarm dashboard, are fixed.

Custom Shortcuts, below the table, bind a key (with Ctrl or Alt) to a snippet that is typed into the active terminal; turn on Run to press Enter after it. They are saved unencrypted in your app data, so don't store passwords or tokens in them.

On macOS, ⌘ works wherever a binding says Ctrl.

See §32 for the complete default list.


10. Agent Capability Ratings

Agent capability ratings

The heart of smart swarm routing. Settings → Agent Ratings lets you score each agent (Claude Code, OpenAI Codex, Gemini CLI) from 1 to 5 in 10 categories:

  1. Refactoring
  2. Architecture
  3. Testing
  4. Documentation
  5. Code Review
  6. Debugging
  7. Frontend
  8. DevOps
  9. Data Analysis
  10. Bulk Tasks

Defaults reflect model-family strengths as of release. You can tune them to match your own experience: each score has its own reset button, and Reset All restores the defaults. The conductor and the smart router treat the scores as hints (the conductor is told which categories each agent scores 4 or 5 in) and still make their own call.

Each agent also carries a relative token cost (high, medium or low) that the conductor weighs for cost-aware routing.


11. Command Palette

Command palette

Ctrl+K (or ⌘K) opens the command palette. Everything you can do from a menu is here, plus a lot that isn't:

Filtered command palette

Type to filter:

  • Actions — "launch claude", "split horizontal", "clear terminal".
  • Workspaces — "switch to ~/work/frontend".
  • Recent commands — from your terminal history.
  • Files — with results ranked by edit recency in the current workspace.

Fuzzy matching is weighted, and exact matches always float to the top. Press Enter to execute, Esc to close, ↑/↓ to navigate.


12. Prompt Templates

Prompt templates

A library of reusable prompts you send to agents. Open with Ctrl+Shift+P.

Built-in templates include:

  • Explain this code
  • Write tests for this
  • Refactor for readability
  • Find security issues
  • Document this API
  • Code review — strict

Each template supports {{variables}} that are filled from the current selection, the focused terminal's working directory, or free-form input. Add your own with the + New template button; they're saved to prompt-templates.json in your data directory.


13. Workflow Orchestrator

Workflow designer

A workflow is an ordered pipeline of steps that Termpolis runs for you. Open the Workflows section in the sidebar and press the inline + (Start Workflow) to author one on a blank canvas. Every saved workflow appears as a row in that section — press it to open the run view.

Steps

Four kinds, in any order:

  • Command — a shell line (inline or a script file) on a real terminal, with its own shell and timeout.
  • Agent — launches Claude Code, OpenAI Codex, or Gemini CLI on a prompt and waits for it to finish.
  • Skill — calls one of Termpolis's own tools: code search, memory, git. The step fails when the tool answers with an error or takes longer than a timeout you set on the step. With the Timeout left empty (none) the step waits as long as the tool takes, and a tool with a limit of its own, such as run_and_wait or agent_exec, keeps it. Skill steps count toward the MCP server's rate limits: linked_machines shares its 30 calls a minute with your agents, while the other limited tools, and a workflow's calls overall, have separate workflow limits, so a looping workflow can't get your agents refused. Every call is written to the MCP audit log.
  • Control — wait, branch, loop, or notify.

Each step takes an optional when gate and an optional continue even if this step fails. Later steps read earlier results (${steps.build.exitCode}, a step's captured output) inside gates, branch conditions, loop guards, and notify messages. Expressions run through a small, pure, sandboxed evaluator — never eval.

Availability, categories, and inputs

Workflow designer — availability, category, and inputs

  • Availability — Project stores the workflow in <repo>/.termpolis/workflows/<id>.yml and shows it only in that repo. Global stores it in your Termpolis data directory and offers it in every project. Switching availability moves the file between the two stores; scope is derived from where the file lives and is never written into the YAML, so moving a .yml by hand re-scopes it.
  • Category — a free-text label (Build, Release/Nightly, …). The sidebar files every workflow with that label into a collapsible folder; uncategorized workflows stay at the top level.
  • Inputs — named values the workflow asks for before it runs. Each has a name, an optional label and description, an optional default, and a required flag. Termpolis collects them in the run view and keeps Run locked until every required input is filled. Reference them as ${inputs.NAME} in any step field, gate, branch, or loop condition.

A global workflow always runs against the directory you're standing in, so ${project.cwd}, ${project.name}, and ${project.branch} resolve to the repo you're actually in — one workflow, reused everywhere, parameterized by inputs.

Triggers

Give a workflow a trigger and it stops needing you:

  • Manual — only when you press Run.
  • Schedule — a real cron expression (0 2 * * *, or @daily) in your local time, with an optional catch-up for a run missed while the app was closed.
  • Git commit — fires when a new commit lands on the checked-out branch, optionally narrowed to one branch. This is the post-commit hook: lint, test, or hand the diff to an agent.
  • Git push — watches the remote-tracking ref for a chosen remote and branch.
  • File change — a debounced recursive watch you can narrow to specific paths.

Triggers survive restarts, and a triggered run takes the exact same path as pressing Run — including the workspace-trust gate, so an untrusted folder never fires. A global workflow arms in every project the app has open, and each project keeps its own trigger state, so a commit in one repo only fires that repo's run.

Watching a run

The run view streams each step's output live, marks it succeeded, failed, or skipped, and shows how long it took. Cancel mid-run and every in-flight step is torn down cleanly. Run history is appended to .termpolis/workflows/runs/<workflowId>.jsonl next to the store the workflow came from.


14. Context Panel

Context panel

Ctrl+Shift+E toggles the context panel, as does Show Context Panel in the command palette. It shows the active terminal's folder:

  • File Tree — the files and folders in it.
  • Git Status — the changed files, or "Clean working tree" ("Not a git repo" outside a repository).
  • Recent Commits — the latest commits, each with its hash and message.

The folder's path is at the bottom. Click a section's header to collapse it. Snippets and notes you want to keep for a project go in the separate pinned context panel (Ctrl+Shift+B).


15. History Search

History search

Ctrl+Shift+H opens terminal history search. It spans every terminal you've ever opened in Termpolis, not just the current shell's history file. Search by:

  • Command
  • Working directory
  • Exit code (e.g. find all failures)
  • Time range
  • Shell type

Click a result to copy, re-run in the focused terminal, or pin to context.


16. Conversation Search

Conversation search

Ctrl+Shift+I opens conversation search — the AI-session equivalent of history search. Search across every agent session Termpolis has recorded:

  • Filter by agent (Claude / Codex / Gemini).
  • Filter by kind (prompt, tool call, tool result, error).
  • Full-text search with highlighting.
  • Time range.

Each hit deep-links into the original session so you can reopen it, re-prompt, or copy a successful flow.


17. Git Panel

Git panel

Open it with the Git Panel button in the sidebar toolbar. It opens on the repository that holds the active terminal's folder; otherwise click Open Folder to pick one, and click the folder path in the header to switch to another. The Git panel is a lightweight GUI for what you usually do at the CLI:

  • Current branch in the header, with Pull, Push and Refresh buttons.
  • Staged Changes and Changes — each file shows its status. Click a file to see its diff.
  • Stage and unstage — the + / − next to a file, or Stage All / Unstage All.
  • Commit — type a message in Commit message... and press Enter or click Commit.

Every action runs as a real git command in a spawned process — no reimplementation — so you can always drop to the CLI and see the same state.


18. AI Agent Profiles

Launch any AI CLI as a profiled terminal: Claude Code, Codex, Gemini CLI. Profiles come pre-configured with:

  • The correct shell + startup command.
  • A color + label for visual distinction.
  • An MCP bootstrap so the agent can control Termpolis.
  • A distinct working directory if you want one.

Custom profiles take any command — if it's in your PATH, you can profile it. Add one with the + button on the sidebar's AI Agents section.

Choosing the model

Two things set which model an agent runs on: a default per agent, and a menu on each running terminal.

Default models (Settings → General → Default Models) pick the model that each of Claude Code, OpenAI Codex and the Antigravity CLI starts on in a new terminal: from the sidebar, the launch shortcuts, the Welcome screen, the command palette, a restored workspace, or a handoff. Agent's own default adds nothing, so the agent's own setting decides.

  • Claude's menu holds its four aliases (Fable, Opus, Sonnet, Haiku), which always mean the newest model in each family, and Auto (best for the task).
  • Auto shows a What's the task? (optional) box above the agents in the sidebar. What you type there picks the model and effort for that one launch (Haiku at low effort for a quick lookup, Sonnet for building, Opus for design and hard bugs, Opus Plan when there is no task) and becomes the session's first prompt. The agent's header shows what Auto picked. An Auto session's subagents run on Sonnet. Without Model per message (below) the model is picked once, at the start: switching models mid-conversation reads the whole conversation again at full price, so that setting switches only when the switch still saves. If your account can't use the model Auto picked, Claude Code says so, Termpolis remembers it for that account, and the next Auto launch there leaves it off. A new install starts on Auto; an existing install keeps its setting and is told once that Auto exists.
  • Model per message (v1.56, under the defaults) runs each message you send to Claude on the model it needs: Haiku for a quick question, Sonnet for building, Opus for design and hard bugs. Haiku is picked only when the local model is clearly sure and the message is short and asks for nothing that sounds like work (making or changing something, a status check, a "why", something you don't see, a file path or a link); otherwise the message runs on Sonnet (v1.56.2). Since v1.57 it picks the effort too (how long Claude thinks: low, medium, high, xhigh or max), routes each subagent's task on its own, and gives a message more thought while its tool calls keep failing. Claude terminals started on Auto, and Codex (the default) routes the Claude terminals that started on Auto and the Codex terminals (below); Every Claude and Codex terminal routes them all; Off routes none. It works in the Claude terminals Termpolis opens with token compression on, because the switch is made in Termpolis's local proxy: the request's model is changed before it leaves this computer, nothing is typed into the terminal and the session is not restarted.
    • Who decides. A small embedding model already on this computer for memory (bge-small) compares the message with 120 labelled examples, and your own corrections, and votes; the word rules Auto uses decide when the vote is unsure. A decision takes a few milliseconds and is never sent anywhere. If the vote is not back within 350 ms the rules decide alone.
    • When it moves. Switching model makes the next request re-read the conversation without the new model's cache, so it moves down only when that still saves at least 10%, moves up only when it is sure and the conversation is under 150,000 tokens (or the target model's cache is still warm), never sends a conversation over 180,000 tokens to Haiku, and keeps a short reply ("yes, go ahead") on the model doing the work. A session's first message moves off the launch model only when the vote is sure.
    • Effort (v1.57). Each model has a range it picks from: Haiku low or medium, Sonnet low to xhigh, Opus medium to max. A quick question runs at low, building at Sonnet's medium or high, design and hard bugs at Opus's high or above. The prompt cache is kept per model and effort, so a change of effort on the same model is weighed like a change of model: a lower effort only when it still saves after the re-read, a higher one only when the router is sure and the conversation is under 150,000 tokens. The effort is written into the request's output_config by the same local proxy; a model's own default is never written.
    • Subagents (v1.57). When a subagent Claude Code starts in a routed pane sends its first request, the proxy holds it for up to 300 ms while the router reads the subagent's task, then sends it on the model and effort that task needs (a file search on Haiku, a design review on Opus). Its conversation is new, so nothing cached is lost. An answer that comes later still routes the subagent's next requests. The main conversation is untouched.
    • Struggling (v1.57). When tool calls fail two turns in a row, the message gets one more level of effort (two after four failing turns), never above xhigh and never on a conversation past about 600,000 characters. It stays raised until your next message, so the cache is not emptied twice. The pane header shows "more thought". The model is never changed for this.
    • What you see. The pane header shows each pick (for example "Opus → Haiku · low · 4 ms", "Sonnet · high → xhigh · more thought", or "Subagent: Haiku · low · 12 ms"); hover it for why, who decided, how sure, and the estimated saving. Its menu, Should have been…, teaches the router: the message's vector (never its text) is kept as an example, weighted double, the newest 300 kept. Picking a model in the pane's Model… menu stops routing that pane until it is launched again.
    • If your account refuses the model or the effort, the request is sent again as Claude Code asked for it, and that model (or any change of effort) is not tried again in that terminal.
    • Limits. A header you set yourself in ANTHROPIC_CUSTOM_HEADERS is kept, but if Claude Code is given that variable from its own settings file the terminal's tag can be lost and its messages are not routed.
    • Codex (v1.58). In a Codex terminal the router picks the reasoning effort of each message you type, never the model (Codex's encrypted reasoning does not carry from one model to another). A quick question runs at low, building at medium or high, design and hard bugs at high or xhigh; a short "yes, go ahead" keeps the effort the work was on, and a vote that is unsure leaves the effort Codex asked for. Termpolis starts the Codex terminal with -c openai_base_url=… pointing at its local proxy, which reads the new message, asks the router (it waits up to half a second for the answer, and otherwise sends the request as asked), changes only the "effort" value in the request, and forwards it to the endpoint Codex would have used: chatgpt.com when you sign in with ChatGPT, api.openai.com with an API key. Every request of that message (its tool calls included) goes at the same effort. Codex's own internal requests (marked x-openai-subagent), the model list and compaction pass through as sent. The pane header shows "Codex · medium → low · 6 ms", and its Should have been… menu offers low, medium or high effort.
      • WebSocket. The proxy answers Codex's WebSocket with "upgrade required", so Codex falls back to plain HTTP for the session (its own fallback), which lets each request be read. Streaming still works.
      • Refusals. If OpenAI refuses the effort (a model without xhigh, say), the request is sent again exactly as Codex asked. If that one goes through, the effort is not tried again in that terminal (xhigh falls back to high), and after three refused efforts the terminal is no longer routed; a request refused both ways (too long a context, say) blames nothing. Since v1.58 the proxy also asks for the same local port (47853) at every start, so a Codex command recalled from your shell's history still reaches it.
      • When it steps aside. Only Codex terminals launched from the sidebar's agents are routed. Codex is launched as it is when its command names its own endpoint, provider or profile (openai_base_url, model_provider, chatgpt_base_url, --oss, --profile/-p), when config.toml in its home sets openai_base_url, model_provider, profile or chatgpt_base_url at the top level (Termpolis reads only that file, never Codex's sign-in), when OPENAI_BASE_URL, HTTPS_PROXY, HTTP_PROXY or ALL_PROXY is set, or when the compression proxy is off (Settings → Token Savings). A command that sets model_reasoning_effort itself is routed only under Every Claude and Codex terminal. A ChatGPT workspace that pins its data to a region may send Codex to another host; that host is not known to the proxy, so such an account is best left on Off.
      • Switching. Changing a routed Codex terminal's model from its header keeps it routed: the relaunch points Codex at the proxy again. Switching it to another account in place does not, because that account's own settings may choose another endpoint: the terminal runs on as Codex asks until you open a new one from the sidebar.
  • Codex's and the Antigravity CLI's menus are read from each agent's own list, never typed in. Codex publishes its list after it has run once; the Antigravity CLI's appears when you press Refresh lists, which asks it for its models.
  • A profile's own pinned Claude model beats the default. A command that already names a model (--model) or resumes a session (--resume, --continue) is left alone. A default its agent no longer lists is skipped, and a notice says so.
  • A terminal that is already running never changes.

The Model… menu in the header of a Claude Code, Codex or Antigravity terminal that Termpolis launched switches that one terminal while you work. It presses Ctrl+C three times to quit the agent, waits until the operating system confirms the agent has exited, reads the line the agent prints as it goes (Resume this session with: claude --resume …, or agy --conversation=…), and starts the agent again on the new model, resuming exactly that conversation. The menu reads Restarting on … meanwhile, which takes a few seconds, longer for an agent that runs shutdown hooks. The terminal takes no key during those seconds, for everyone: keys typed while the relaunch types would sit on the shell's line and run with its Enter. When you have accounts, the pane is dimmed and says Changing model… meanwhile; with none added, nothing but the menu's own words changes.

  • Switch before anything has been said and there is nothing to resume, so the agent starts a new session on the new model, and a notice says so. (Asking Claude Code to continue a conversation that does not exist only gives "No conversation found to continue", and the Antigravity CLI would pick up a conversation from another project.)
  • If Termpolis cannot tell which conversation was running (it could not read the agent's exit line, the agent had already exited, or the OS could not see it), Claude Code and Codex resume their most recent conversation in that folder, and if that leaves nothing running, a new session is started and a notice says so. The Antigravity CLI is never asked for its most recent conversation, because that one is global and can belong to another project: it starts a new session, says so, and your earlier conversation is still saved. Codex's exit line has not been confirmed against the real CLI, so Codex takes the most-recent route unless its line is recognised.
  • Launch flags carry over: --dangerously-skip-permissions or --yolo, for example, with the value of each flag that has one. A launch command that holds quotes, shell operators, variables, or a prompt (claude /review) cannot be repeated safely, because it could re-submit the prompt or change what a flag means. Then the agent comes back with its default options, and a notice says so. (A quoted value that is replaced anyway, such as --model 'opus[1m]', does not get in the way.)
  • The account stays, and the terminal is held. The shell still has the account set, so the agent comes back on the account it was on, and while the menu works nothing else can type into the terminal: an account switch waits, an MCP tool's or the phone's writes are refused, and the keys you type are held. An account switch in turn restarts the agent on the model it was last started with.
  • A terminal started by a wrapper such as claude-sandbox or claudex is not restarted. Its command starts with an agent's name but is another program, and the bare agent would run without whatever the wrapper adds, a sandbox included. A notice says so; open a new terminal on that model instead.
  • If the agent has not quit after 30 seconds, Termpolis types nothing more and tells you. If it never quits, it is still on its previous model.
  • The menu appears only for terminals Termpolis launched. It sends Ctrl+C to whatever is running in the terminal, so if you quit the agent and started something else there, leave the menu alone.
  • Switching again within a few seconds, while the agent is still starting up, is the least reliable moment: wait until it has finished starting.

Second Opinion

Want another model to check what your agent just did? Every AI terminal has a Second Opinion… menu in its header. Pick a provider, or one of its models, from the agents you have installed (Claude Code, Codex, Gemini). The menu groups models by family: the Claude models the Antigravity CLI offers are listed under Claude, marked via Antigravity, and the Claude group shows even when only the Antigravity CLI is installed. Claude · default (Sonnet) reviews on Sonnet at medium effort; pick another Claude model to use that one instead. Termpolis sends the last 160 lines of the terminal's output to that agent for review, and the menu reads Reviewing… until the answer comes back.

The feedback is pasted into the terminal between a === Second Opinion (…) === line and an === end second opinion … === line, but not sent, so you can read it first. Press Enter to pass it on to your agent, or clear it.

Since v1.49 the reviewing agent is read-only. It runs headless and can't change anything:

  • Claude Code runs in plan mode with no tools. Bash, PowerShell, Edit, Write and NotebookEdit are also explicitly disallowed, and none of your MCP servers are loaded.
  • Codex runs as codex exec --sandbox read-only.
  • Gemini runs through the Antigravity CLI in plan mode (agy --mode plan).

A review gets 90 seconds. Termpolis stops one that runs longer, and any still running when you quit, and since v1.49.1 it ends the agent's whole process tree, so on Windows nothing the agent started keeps running in the background.

The terminal output never passes through a shell's command line (on Windows it travels in a temporary file), so text in your terminal can't become a command.

A review gets 90 seconds. If it runs out of time, or is still running when you quit Termpolis, Termpolis ends the reviewing agent and everything it started, so nothing is left running in the background.


19. MCP Server

Termpolis ships an MCP (Model Context Protocol) server so AI agents can control the app from inside a conversation. It listens on http://127.0.0.1:9315 — reachable from this machine only — and moves up to the next free port, as far as 9319, if that one is taken.

Available tools

The tools come in seven groups. The authoritative list is the one your agent shows for the termpolis server (for example /mcp in Claude Code), because it comes straight from the running app.

Group Tools What they're for
Terminals and files list_terminals, create_terminal, run_command, run_and_wait, read_output, write_to_terminal, close_terminal, get_file_tree, get_git_status Open, drive and read terminals, run a command to completion and get its exit code, list files, and read git status and recent commits
Swarm swarm_send_message, swarm_read_messages, swarm_create_task, swarm_list_tasks, swarm_update_task, swarm_list_agents Message other agents, keep the shared task queue, and see which agents are running
Memory memory_write, memory_search, memory_list, memory_primer, memory_related, memory_graph, memory_link, memory_audit, memory_anticipate, memory_feedback, memory_selfcheck, memory_pool, memory_conflicts, memory_correct Read and write the shared memory, follow its knowledge graph, and rate, correct or audit what it recalls
Code intelligence code_search, code_locate, code_explore, code_callers, code_callees, code_impact, test_coverage Find symbols, predict where a bug lives, trace callers and callees, size the blast radius of a change, and read which lines the project's last coverage run covered
Gateway gateway_list_tools, gateway_call Reach tools on the MCP servers you add in Settings → MCP Servers, under its gateway policy
Token Headroom retrieve_full Expand a tool result that Token Headroom shortened
Linked machines linked_machines Have a Claude, Codex or Gemini agent on another linked machine do a task headlessly and return its final answer: list the machines, run a task, and fetch the result of one still running. See §30

write_to_terminal writes exactly the text it is given, at once, and presses nothing. Pass submit: true to have a prompt typed into an agent the way a person would: Termpolis waits until the agent's screen, read at the terminal's own size, shows it ready for input (up to 3 minutes, so a Codex still loading its MCP servers, which would only queue the prompt, gets the time it needs), types the text without its trailing carriage return, waits until the agent shows it in its input, the same on two looks in a row (an agent that has just started can take a second or two, and Antigravity draws a long paste over a few seconds), presses Enter on its own, and checks that the agent took the prompt. Termpolis reads both off what each agent shows at any size: its input box empty (its prompt mark alone, or with the hint it shows until something is typed) and its busy line, where the agent draws it (its footer, its status line, Codex's notice of queued input; an answer or a file that quotes "esc to interrupt" is no busy line). Typed means the box is no longer empty and holds the end of the text, or the agent's placeholder for a long paste, since a long prompt can be taller than the box or the screen, and only its end is then in sight. The text is compared by its letters and digits, which every agent keeps as it is typed: on Windows, Codex and Antigravity drop punctuation and symbols such as "—", "✓" or curly quotes. Taken means the agent shows it is at work, or is back at its empty input box with something new above it. When the text is still in the input, the agent at its prompt, Enter is pressed once more; on anything else, a dialog the prompt opened among them, no Enter is pressed. When the input shows nothing but this text, or the end of it, from an earlier call whose Enter didn't take, it isn't typed again, blank lines in it and all: only Enter is pressed. A paste that Claude Code or Antigravity shows as "[Pasted text #1]" could be any text, so it is cleared and typed again, while Codex's placeholder names the paste's length, counting the characters it kept, and a text of that length gets only Enter. Anything else in the input, a prompt an earlier call left unsent, one a person typed or another paste, is cleared before the text is typed, so that it does not go with this one: Termpolis writes Ctrl+U, which in Claude Code, Codex and Antigravity alike deletes the input back from its cursor to the start of the line (or the line break before it) and sends nothing, twenty to a write (many more read to Claude Code as a paste), and after each write waits up to 3 seconds for the input to show the change, until it is empty. What two writes in a row leave as it was on one row is nothing Ctrl+U deletes: a prompt Claude Code suggests there, which goes at the first key, or what was behind a cursor a person moved back into the text. The text is typed over it, and if anything is still in front of the text or behind it then, no Enter is pressed. On more rows than one, nothing is typed: what is left can be lines behind the cursor, or a text taller than the input whose rows look the same after a write shortened it. Only text on one row in front of the cursor that Ctrl+U doesn't delete, which none of the three agents leaves, would still go with a text taller than what the input shows, out of sight in front of it. An input still changing after ten writes gets nothing typed, and the answer says to send the text again, which clears more of it. Nothing is typed at a shell's prompt in a terminal whose last run_command started an agent: the agent exited or never started, and the shell would run the text as a command. Termpolis reads both from the terminal's output since that run_command typed the command. The agent's input is an input box drawn since then, so right after run_command starts an agent again the call waits for the new one to draw its input, and the last screen the agent before it left when it exited, box and all, is never taken for the new one's, not even when a resize paints it again before the new one draws: the command then shows under that box, at the shell's prompt, even where the repaint draws the box's closing rule and the row under it as one row (it can when Claude Code shows its command menu there). The agent is gone once its shell prints a prompt since then (one it printed first, still starting when the command was typed, doesn't count, nor one the command shows after, typed while the shell was still printing it, nor one followed by a switch to the alternate screen that still holds, as an agent in its fullscreen view does, started again by hand), and a resize that paints that last screen again changes nothing. The command counts this way only where it says more than the agent's name, which a prompt can show too, a folder's (the conductor's commands always do). A prompt is seen where Termpolis's shell integration reports the folder at each one (PowerShell, cmd and bash, unless the integration is off or a bash PROMPT_COMMAND of your own replaces it); elsewhere (zsh, fish), and for an agent you started yourself, the screen alone can't tell the last screen Claude Code or Antigravity leaves from its input. So before anything is typed into an agent's input, Termpolis also asks the OS what runs in the terminal, and types nothing when nothing runs there but its shell: on Windows, the processes on the terminal's console, read by the same node-pty helper that runs when a terminal closes (PowerShell, cmd and Git Bash; not WSL, whose programs aren't on it); on macOS and Linux, the terminal's foreground process group (bash, zsh, fish, sh and ksh, which give each program they run a group of its own; not PowerShell or Nushell). Where the OS can't say, the screen's answer stands. Calls for the same terminal take turns, so two prompts sent at once arrive as two prompts. A call whose client gives up on it types and presses nothing more: the client cancelled it (an MCP client does when a call times out) or closed its connection, or the agent that made it stopped. The answer is { submitted, waitedMs }, or submitted: false with the reason and, when the reason is what the agent showed, the last 15 rows of its screen as screen: the terminal is gone, the agent never became ready, it is blocked (a usage limit, a menu waiting for an answer, a restart), its terminal is back at the shell's prompt, it never showed the text, its input held other text that clearing didn't remove or more than one call clears, it left its input while that was cleared, it neither started working nor went back to its prompt with the text taken after Enter (check it before sending the text again: it may have taken it), or the text was still in its input after the second Enter.

The server is authenticated via a token generated at each launch, which Termpolis writes to mcp-token in its data directory, next to mcp-port. Both files are readable only by your account: mode 600 on macOS and Linux; on Windows an access list that removes what the folder passes down and grants your account full control, so other local users get nothing (entries set on the file itself, which can include SYSTEM and Administrators, who can read every file anyway, are left alone). An agent's Termpolis MCP entry starts a small stdio adapter that reads both. Misuse resistance includes tight origin checks, rate limits, and an audit log (mcp-audit.log). A workflow's Skill steps call the same tools in-process and are held to the same limits and written to the same log, marked "via": "workflow".

Connecting your coding agents

Termpolis doesn't register itself with an agent until you say so. The first step of the first-run tour, or Settings → Agent Integration at any time, lists exactly what it would change for each of Claude Code, Codex and Gemini CLI installed on this machine. Connected, Termpolis:

  • Claude Code — adds the termpolis MCP server at user scope in ~/.claude.json, as claude mcp add -s user would (under your CLAUDE_CONFIG_DIR if you set one). It allows 27 read-only and memory tools in settings.json so they run without asking; tools that run commands or type into terminals still ask. It marks each folder you open an agent in as trusted — never your home folder, a folder above it, a drive root or a network share root. Optionally, it adds a SessionStart hook that loads your project memory whenever a Claude Code session starts, including sessions started outside Termpolis. Since v1.49.1 the hook does nothing once Termpolis is gone, so removing the app without disconnecting first doesn't leave Claude Code with a failing hook (an existing hook is upgraded the first time v1.49.1 starts).
  • Codex — adds the termpolis MCP server to its config.toml (a different server you already named termpolis is left alone), pre-approves the 14 memory_* tools unless you already chose a setting for them, and answers the folder-trust prompt for a folder you open it in, with the same exclusions. Its memory instruction is passed on the launch command (-c developer_instructions) for that session only, and not at all if you set your own; nothing is written into your projects.
  • Gemini / Antigravity CLI — adds the termpolis MCP server where the Antigravity CLI (agy, which the Gemini profile runs) reads it, ~/.gemini/config/mcp_config.json, and lets the same read-only and memory tools Claude Code gets run without asking, in ~/.gemini/antigravity-cli/settings.json (rules like mcp(termpolis/memory_search), never a wildcard). Each file is written only once agy has created its folder. It also keeps the entry in Gemini CLI's settings.json for the older CLI. Disconnect removes exactly these entries and rules, and nothing agy wrote.

Connecting is also what lets Termpolis read Claude Code's saved notes for each project into memory. That is a read of ~/.claude/projects, not a write to any agent's settings, and it has a switch of its own (Settings → Memory & Learning); see Claude Code's saved notes for exactly what is read and how to undo it.

When Termpolis answers a folder-trust prompt, it selects the trust option itself instead of pressing Enter on whatever is highlighted. It never answers a permission, approval or MCP prompt, a [Y/n] question or a numbered choice — those wait for you — and while your agents aren't connected it doesn't answer the folder-trust prompt either. On an agent's first-run screens it still presses Enter and takes the default: Claude Code's intro splash, its theme and login-method pickers, and Gemini CLI's terms and sign-in screens. Once an agent has two accounts or more, Termpolis no longer answers its login-method picker or sign-in screens, and while it relaunches an agent on another account it answers nothing in that terminal: which account to sign in is yours to choose.

More than one account. An account you add in Settings ▸ Accounts has a profile of its own, and you connect it there, on its own: Termpolis then writes into that profile (its own .claude.json and settings.json, or its own config.toml) what it writes into your main one. Folder trust follows each terminal's account: Termpolis pre-trusts a folder, or answers a trust prompt, only for an account you connected. An account not offered automatically, such as a work account, isn't connected until you tick Connect for it. Disconnect and uninstalling take Termpolis's entries out of every account's profile too, including the profile of an account you removed.

Disconnect in Settings → Agent Integration removes everything Termpolis wrote. Uninstalling on Windows or removing the Linux .deb does the same (an update doesn't), and Termpolis --disconnect-agents does it from the command line on any platform.

Whatever you choose, v1.49 also cleans up once after older versions: it removes a permission that let every Termpolis tool run without asking, the duplicate Termpolis plugin for Claude Code, Termpolis's entry in ~/.mcp.json, and any trust for your home folder or a drive root.

Known limitations

  • Disconnect un-trusts only the folders this version marked trusted. Trust that an older Termpolis version added can't be told apart from trust you accepted yourself, so it stays.
  • Older versions wrote a memory note into AGENTS.md in project folders. Termpolis removes that note (and the file, if the note was all it held) the next time you launch Codex from Termpolis in that folder, not before.

20. Swarm Dashboard

Swarm dashboard

Ctrl+Shift+S opens the swarm dashboard — the nerve center for multi-agent work. It has three tabs: Tasks, Messages and Trace. A fourth, Review, appears once a swarm has recorded the commit it started from.

Tasks tab

Swarm tasks tab

Every task in the current swarm run, in three columns: Pending, In Progress and Completed (failed tasks land there too). Each card shows the task's title and description, the agent it's assigned to, and, once it's finished, a summary of the result.

You can also move a task along by hand: Start or Cancel a pending task, and mark one in progress Done or Fail. A cancelled task counts as failed.

Messages tab

Swarm messages tab

A live stream of every message the conductor sends, every broadcast, every handoff. Think of it as the "Slack channel" for your agent team — useful for debugging, reviewing, or understanding exactly how a decision was made. Termpolis's own notes come from conductor and system at launch ("Conductor analyzing task: …", "Conductor launched in …"), and from system after that: that the conductor exited (see When the conductor exits) or refused the task, or that a worker is blocked. A message sent through MCP comes from the name its sender gives: the conductor's instructions have it give conductor, and one that gives none comes from mcp-client. A name is read in any case, without invisible or control characters (the ones Unicode calls default-ignorable, such as variation selectors, among them), and with fullwidth and other compatibility forms of letters folded to the plain ones. To tell system and conductor from other names, any character that Unicode's list of confusables (version 18.0.0) gives as drawn like one of their letters, in either case or as its small capital, is read as that letter too, whatever its script, digits such as 0 and symbols such as ⊤ among them, and so are the four small Cherokee letters the list leaves out, drawn like capitals it gives; ꭃ, drawn like co, is read as co, and rn as m. Only Greek's lunate sigmas, drawn like c, are not: they are compatibility forms of sigmas, folded to those first. A name is read that way only to tell those two: any other is filed as written. So Conductor and c0nductor are the conductor, and no one can send one as system that way, or by spelling it with such characters.

Messages from a Codex or Gemini worker by name, such as Codex, are what Termpolis read off that worker's screen, for the conductor to act on. Its terminal is read every 5 seconds and drawn the way a terminal would draw it, and a message carries only what the screen settled on: lines of the worker's output that stayed the same from one read to the next or scrolled off the top, each posted once; its status line, without the spinner frame and the timer (Status: Reading file...), once it holds; and, once the worker is back at its prompt after working on a prompt it was sent, "screen suggests it may be done; check with read_output". The worker's banner (beside its logo, or on rows of its own, as Antigravity draws it again on a narrow screen), input box and footer, the prompts it was sent (every row they wrap onto), and the shell it runs in (its prompt, and the line that started the agent, however many rows either takes) are left out, and nothing is posted for what it showed before Termpolis first looked. The screen is drawn at the size of the worker's terminal, narrow or wide, and a resize later on starts it over at the new size, larger or smaller, on Windows, macOS and Linux alike. These messages are hints, all typed info: nothing a worker prints completes its task or ends the swarm.

Trace tab

Swarm trace tab

A timeline of what the conductor is doing: the tool calls it makes, the tasks it assigns and sees completed, what it writes as it works, and any errors. It starts with the conductor's first step. Termpolis starts the conductor's Claude Code session under an id of its own (claude -p --session-id) and follows that session's transcript from its first line, in the folder Claude Code keeps it in: under the CLAUDE_CONFIG_DIR the conductor's shell gives claude, which your shell profile can set, else under ~/.claude. So no other Claude Code session shows up in it, not even one working in the swarm's folder, such as the last swarm's conductor or a Claude Code worker.

Review tab

The changes the swarm has made since it started, file by file. Accept or reject each hunk and run a test command, then commit the changes you accepted, or revert everything the swarm changed. See Swarm Review Panel.

Debug

Debug, at the right of the tab row next to Clear, shows what the conductor's hidden terminal shows, as plain text: the last 32,768 characters it printed, which for a short run is everything from the shell's startup on. It is read-only and refreshes every two seconds, and it follows new output until you scroll up. The button is there while a conductor terminal exists: during a swarm, and after the conductor exited or refused a task, until you clear the swarm or start a new one. Debug doesn't unhide or move any terminal, so it works the same in tabs view and split view.


21. AI Conductor

The conductor is a dedicated Claude Code instance that runs headless in a hidden terminal (the dashboard's Debug button shows that terminal's output), with instructions purpose-built for orchestration. It:

  1. Reads your task, the project folder, and the installed agents, with each one's strongest categories and relative cost.
  2. Posts its plan to the swarm's messages.
  3. Creates a task record for every subtask, so the plan shows up on the dashboard.
  4. Opens a terminal for each subtask and starts an agent in it (see Swarm permission flags).
  5. Types each agent's task prompt into its terminal with write_to_terminal's submit, which waits until the agent is ready for input, then presses Enter (see Available tools).
  6. Checks the task list, the messages and each agent's output every 15–20 seconds, and sends guidance to an agent that looks stuck.
  7. Marks each task completed as its agent finishes, then posts a final summary.

The conductor is not keyword matching — it reasons with the same capability as any frontier model, because it is one. Follow what it's doing in the dashboard's Trace tab.

A task is completed or failed only by the conductor (swarm_update_task) or by you (Done or Fail in the Tasks tab), never by what a worker's screen says. The swarm ends when every task is completed or failed, or when the conductor posts its final summary, a result message from conductor that starts with SWARM COMPLETE, or from mcp-client (a sender that gave no name) if, when it was posted, no task existed yet and no worker's terminal was open, this swarm's or one a swarm before left running (one that closed at most 2 seconds before still counts as open). A worker's own message in those words ends nothing, not even once its terminal has closed. So the conductor's final summary without its name, posted while a worker's terminal was open, ends nothing either: the swarm then ends when its tasks do, or with the conductor's exit report.

Starting a swarm

Start swarm wizard

Click Start Swarm on the welcome screen, or in the swarm dashboard's header while no swarm is running, and pick the folder the swarm should work in. The wizard asks for:

  • Goal — what you want built, in plain language. It's the only required field.
  • Constraints — platforms, languages, or anything else to avoid or insist on.
  • Expected Output — what "done" looks like.
  • Failure Conditions — what would count as a failed result.

Click Launch Swarm. The conductor spins up, reads the task, and the dashboard populates with subtasks within seconds. The conductor itself runs on Claude Code, so a swarm needs the claude CLI installed.

Termpolis writes the conductor's instructions, with your task in them, to .termpolis-conductor-task.md in your home folder, and a run script next to it: .termpolis-conductor-run.ps1, which runs in Windows PowerShell 5.1, or .termpolis-conductor-run.sh for bash on macOS and Linux. The script finds a claude that answers --version, then pipes the task file into claude -p --mcp-config <file> --session-id <id> --dangerously-skip-permissions on standard input, as UTF-8. The id is a new one for each swarm, and names the session the Trace tab follows. Before it starts claude, the script writes the id and the CLAUDE_CONFIG_DIR its shell has (none, mostly) to .termpolis-conductor-claude-home beside the task file, which is how Termpolis knows where that session's transcript will be. Only those flags go on the command line, so double quotes, words that look like options (--watch, -NoProfile) and characters such as — or ✓ reach the conductor exactly as you wrote them, and no command-line length limit applies to the task. The .ps1 is saved as UTF-8 with a byte order mark, so Windows PowerShell 5.1 reads the paths in it right whatever your user name holds (José, MUÑOZ, O’Brien). Where AppLocker or WDAC runs it in Constrained Language Mode, which can't set those encodings, the script hands claude the task file itself as its standard input, so the task reaches it exactly as written there too. It starts the claude.cmd npm installs beside its PowerShell shim; only a claude.ps1 with no claude.cmd beside it is still piped to, and gets the task with a byte order mark in front, which the conductor's output then says.

When the conductor exits

The run script prints [termpolis-conductor-exited] and claude's exit code on a line of their own when the conductor exits, and Termpolis watches the conductor's terminal for that line.

  • It exited with an error, or before it created any task. The swarm ends. The Messages tab gets Conductor exited (code N): followed by up to the last 15 lines the conductor printed, an error banner appears, and you can start a new swarm. When no working claude is found, or the one found never started, the script reports exit code 1 with the reason.

  • It exited cleanly after handing out tasks. The swarm keeps going: a message says how many tasks are still open, and the swarm ends when every task is completed or failed. You can mark one yourself with Done or Fail in the Tasks tab.

  • It exited at its usage limit. When Claude Code's own usage-limit message is among the conductor's last lines, and another Claude Code account is a way out (by the usage-limit card's rules, and one that skips the bypass-permissions warning its --dangerously-skip-permissions would open), a card offers Run the conductor again on . Termpolis then runs it again in its terminal on that account: it resumes its own session by its id when both accounts can see its transcript (each is your main profile or shares its projects folder), and otherwise starts anew with the same goal, told that a swarm is in progress and to read the task list and the messages before it creates tasks. A resume whose transcript is not there (Claude Code answers "No conversation found with session ID" and exits) is run once more as a new session on the same account. The swarm's workers and tasks stay.

What the conductor says as it finishes doesn't change that. Termpolis reads its output for a refusal of the task or for a run without its swarm tools only when it created no task and didn't exit with an error: a swarm whose tasks are done ends as done, even when the conductor's last words are "I can't verify the deployed site".

If, a minute after launch, the conductor has created no task and posted no message, a banner says so. Only the conductor's own messages count, not Termpolis's launch messages or what a worker's screen showed; one from mcp-client counts if, when it was posted, no task existed and no worker's terminal was open (counted as for the final summary above), since nothing else can have sent it, and it still counts once a worker starts. One posted while a worker's terminal was open never counts, even once that terminal has closed. Press Debug to see what it printed.

Starting a swarm sets aside the workers a previous one left running: they leave the dashboard's agent list, and Termpolis no longer passes on what their screens show or reports their stops, which would reach the new conductor as if they were its own. Their terminals keep running until you press Clear.

When a worker hits a usage limit

Another account first. When a Claude Code or Codex worker runs out of usage on an account Termpolis knows, and another account of that agent is a way out, Termpolis offers to restart the worker there before the conductor gives its task away. A way out is what the usage-limit card would offer: offered automatically (never a work account), signed in now to another email, not out of usage, and not out of usage on the model the worker hit. It must also be connected to Termpolis, so the worker keeps its tools, and, for Claude Code, skip the bypass-permissions warning a worker's --dangerously-skip-permissions would open (an account's card says when it doesn't). A card offers the restart, grouped by the account that ran out ("3 of the swarm's workers ran out of usage on personal" [Restart all on codedev] [Let the conductor reassign]), in the swarm dashboard, on a worker's pane on screen, and otherwise at the window's bottom right, with an OS notification while the window isn't in front. A signed-out account of that agent is listed on the card as Sign in to and restart beside a real one, never alone: with only a signed-out account nothing is offered and the conductor hears the report below at the stop. Nothing is typed into a worker before you click. Meanwhile system tells the conductor to wait:

(, terminal ) is blocked: usage limit reached (resets 3pm). Termpolis is offering to restart it on another account, where it keeps its conversation and its task. Don't close it or give its task to another agent yet: system will post again.

and the conductor's instructions say to wait for that next message. Then, one message per worker:

  • Restarted, its conversation resumed. The worker is relaunched in its own terminal, keeping its terminal, name and role, with its own command on the other account: "… was restarted on another account and resumed its conversation. It is waiting at its prompt: send it 'Continue your task.' with submit=true (STEP 5)."
  • Restarted without its conversation, when the other account can't see it: "… restarted on another account without its conversation. Send it its task again with submit=true and the same taskId (STEP 5)."
  • Declined, failed, or no answer within 5 minutes: the report below, as before. The clock stops at your first click, the conductor is never told to reassign a worker while its restart is under way, and a restart is refused once the conductor has closed the worker or given its task to another agent.
  • Closed before it was restarted, when you closed the worker's pane (or its restart found it gone) while it was on the offer: " (, terminal ) was closed before Termpolis could restart it. If its task is still open, give it to another installed agent." Nothing is said of a worker the conductor closed itself.

While a worker restarts, the conductor's write_to_terminal, run_command and submit on it are refused with "Termpolis is restarting this worker; wait for system's next message". A restarted worker that shows its old limit again isn't reported until it has taken a turn (the stop it carried ends once it has been read at work on two reads in a row, 10 seconds, not one: the resumed session can draw a frame that reads as work before it settles on its old limit): the swarm dashboard shows it waiting, not blocked, and the conductor's write_to_terminal with submit to it is typed, not refused as blocked. A limit it shows after that turn counts again. Leaving an account that isn't offered automatically asks once more first. A Claude Code or Codex worker whose agent has two accounts or more also has an account chip in the swarm dashboard, to switch it by hand; the conductor is told the same way. With no way out (no other account signed in, offered and not out of usage; a signed-out one alone is not one), nothing changes, and the stop is reported as follows. The offer reads a worker's screen as the report does (v1.54.0's accepted limit): a healthy Claude Code worker whose answer quotes a usage-limit message can read as stopped, and the offer then records that limit against its account until its reset time, or until you press Mark available on the account's card, or until the worker is read at work again (it was not stopped, or its limit lifted), when Termpolis takes back what its stop recorded; a restart you chose keeps the record. Nothing is typed into a worker without your click. When a restart fails after the worker's agent was ended (the account line never showed, the other account's sign-in expired, a question only a person answers, nothing within 30 seconds), the conductor is told so, and that the limit is no longer on the worker's screen: it closes the terminal and gives the task to another agent without looking for the limit.

Termpolis reads every worker's screen every 5 seconds. When a worker shows its plan's usage limit, or a menu that holds it until someone answers, the dashboard says the worker is blocked and why, for example "usage limit reached (resets 3:04 PM)" or "waiting on a menu: Codex asks whether to switch to a model that uses fewer credits", and system posts the same to the conductor in the Messages tab, with the worker's terminal and, when the task was assigned to it, the task. The message is addressed to the conductor, so no worker reading the swarm's messages takes it first. It tells the conductor to look at the worker's screen with read_output first, and to act only if the limit or the menu is there: a report read off a screen can be wrong, and closing a healthy worker ends its task halfway. For a usage limit, it tells the conductor to close that worker's terminal, since a limit can lift and the worker carry on by itself, and to give its task to another installed agent. For a menu, it names the option to pick and the keys that pick it ("Skip until next version" on Codex's update offer, "Keep current model" on its offer of a cheaper model): a few key presses clear a menu, and the worker keeps what it has done. The conductor sends the task again if the worker never got it, and gives it to another agent only if the menu stays. What counts as a usage limit for a worker is what the usage-limit card counts: besides a plan's limit, Claude Code's "Your seat type doesn't include usage", "Your usage allocation has been disabled by your admin" and "Your group's usage limit is set to $0", and Codex's "Your workspace is out of credits" and workspace spend cap are stops an admin or a workspace puts on a seat, and a worker that shows one is reported like any other. Codex 0.164's empty composer (a status line and a hint row under it) is read as empty, so a worker waiting at it is no longer taken for one holding typed text. Each stop is reported once, a reset time that counts down included, and a moment when the screen doesn't show it, as while a resized worker's screen is drawn again, doesn't make it a new one; a worker that gets going, or shows no stop on two reads in a row, and stops again is reported again.

What counts is only what a CLI draws in its own rows, in its own layout, at the bottom of the screen: in place of its input box, under it, Claude Code's status line, and the limit message a CLI ends its turn with, the last thing above its input box, in the cell only that CLI draws there. Never the same words anywhere else, however much a row looks like the CLI's message: a worker's answer, a file, a diff or a log it shows can hold them, and a false report is worse than a missed one, since it tells the conductor to close a worker at work. That is Claude Code's rate-limit menu ("What do you want to do?" over "Stop and wait for limit to reset" and the like, "Enter to confirm · Esc to cancel" under them), which it opens by itself in place of its input box when a limit with a reset time stops a session below the Max 20x plan, with the limit's message ("You've hit your session limit · resets 3pm") right above it; its status line while it waits out a limit ("Usage limit reached · continuing automatically at 3pm"), and a limit message in its footer under the box; Codex's prompts in place of its composer, "You've reached your workspace credit limit" and "Usage limit reached" with "Request a limit increase from your owner"; Gemini CLI's quota dialog, "Usage limit reached for …" (for any Pro model, "for all Pro models") in its frame, however tall the dialog is; and the messages the CLIs end a turn with, their input box back under them: Codex's error cell ("■ You've hit your usage limit. … or try again at 3:04 PM.", and the same for one model), Antigravity's ("✕ You have exhausted your quota on this model."), and Claude Code's reply when its menu doesn't open, on the Max 20x plan, with an API key or on a Team plan ("⎿ You've hit your session limit · resets 3pm"): right under the prompt it stopped, even one so tall that its first rows have gone off the top of the screen, or, when the limit stops it mid-turn, right under the output of the tool it ran last, that tool's call still on the screen, or right under the row Claude Code folds the turn's reads and searches into ("Read 1 file (ctrl+o to expand)"), the text of the turn above it. Under a prompt taller than the screen, and mid-turn, the reply is told from an answer that quotes one by the space after its ⎿, which Claude Code draws as a no-break space and an answer is taken to type as a plain one, and mid-turn also by a row of the quote led by ● (⏺ on macOS), as Claude Code leads a turn and a tool's call. So an answer that ends its turn on such a quote with the no-break spaces kept, as read_output returns them, is reported as a limit: one that quotes the reply with the tool's output or the folded row above it, and no row led by ● or ⏺, or one taller than the screen whose rows left on it are its text and the quoted reply. A limit right after a tool Claude Code draws no row for, an update of its to-do list or a search for a tool, is not told: its reply is then right under the text of the turn, where only the words of that text would tell it from a tool's output, and it is told once the worker draws the reply again, under the next task it is sent. Such a message counts while the agent is at rest with nothing after it but the line Claude Code draws when a turn is done ("✻ Crunched for 12s · done 3:04 PM"): a prompt or an answer after it makes it history. It is read whole however many rows a narrow screen wraps it onto, as long as the CLI's input box is still told under it, which Claude Code's and Antigravity's isn't below 20 columns. The menus are Codex's offer to switch to a cheaper model when a limit is near ("Approaching rate limits" over "2. Keep current model") and its offer to update itself, each read by its title on a row of its own, its options with the cursor on one, and its keys under them. A warning that a limit is near, such as "You've used 82% of your weekly limit", stops nothing, nor does Claude Code's notice that fast mode ran out (it switches fast mode off and carries on). Termpolis doesn't move the task itself.

Swarm permission flags

Swarm agents run unattended, so the conductor starts each one with flags that keep it from stopping to ask for approval. These are the only agent launch commands a swarm terminal accepts:

Agent Command
Claude Code claude --dangerously-skip-permissions, optionally with --model fable, opus, sonnet or haiku
Codex codex -a never -s workspace-write -c check_for_update_on_startup=false
Gemini agy --dangerously-skip-permissions (the Antigravity CLI)

If the conductor starts one of these agents any other way — with a prompt, -p, a sandbox flag or anything else added — Termpolis rewrites the command to the one in the table before it runs, keeping only a valid --model for Claude. The older gemini CLI is still accepted as a fallback, but only as a plain gemini with nothing after it.

  • Claude Code workers keep --dangerously-skip-permissions by design. No one is watching a worker, so a single approval prompt would stall the whole swarm. The conductor runs Claude Code the same way.
  • Codex workers run with -a never -s workspace-write. -a never means Codex never stops to ask: a command that would need approval goes back to the model as failed. -s workspace-write keeps Codex's sandbox, so it writes only inside the project and, by default, has no network access. Codex 0.153 rejects the old --full-auto flag. -c check_for_update_on_startup=false turns off Codex's check for a newer version: with it on, every Codex started while one is out opens on an offer to update, a menu that holds it until someone answers.
  • Start a swarm only in a folder you're happy for agents to change. The Review tab lets you accept, reject or revert what they did.

Outside a swarm, Termpolis starts an agent without permission prompts only when you ask for it: a headless run from the Termpolis CLI with --write (termpolis-cli exec "<task>" --write). Headless runs are read-only otherwise, and Second Opinion is always read-only.


22. Activity Feed

Activity feed

The activity feed is the observability layer for every agent, every session. Press Ctrl+Shift+A to open it.

Event types

  • message — text output from the agent.
  • tool_call — when an agent invokes a tool (with args).
  • tool_result — the result of a tool call.
  • token_update — token usage deltas.
  • compaction — when an agent compacts context.
  • error — agent or tool error.
  • status_change — idle → working, etc.
  • mcp_audit — every MCP request + response.

Filters

Three filter rows: search (full-text), kind (dropdown), agent type (dropdown). All combine.

Scoped vs global

With a terminal active, the feed shows that terminal's session, with the intervention controls above the events. With no terminal active, it shows every agent across every session. The header tells you which: "Agent Activity (terminal)" or "Agent Activity".


23. Intervention Controls

Every scoped Activity Feed includes a row of intervention controls above the event list:

  • Pause — sends ESC (0x1B) to the agent's pty, which most CLIs interpret as "cancel current input".
  • Cancel — sends a single Ctrl+C (0x03).
  • Interrupt — sends a double Ctrl+C (0x03 0x03), which Claude Code and Codex treat as a hard stop.
  • Steer — a text input with a send button. Type a new instruction and the agent receives it directly at the prompt.

The rationale: every agent is a pty, so writing control characters or text to its stdin is the fastest, most reliable way to take over. No new IPC surface — just the pty API we already have.

The controls show the last action you sent, for example "Hard interrupt (Ctrl-C x2)". Interventions aren't added to the event list.


24. Swarm Review Panel

When a swarm starts in a git repository, Termpolis records the commit it started from. Once the swarm finishes, the Swarm Review Panel shows everything it changed since that commit, so you decide what to keep. It's the dashboard's Review tab; Review Changes in the Swarm Complete dialog opens the dashboard for you. A folder that isn't a git repository has no starting commit to compare against, so it gets no Review tab.

  • Files and hunks. Select a changed file to see its hunks, then Accept or Reject each one. Accept all and Reject all decide every hunk at once. Reject entire file puts that file back the way it was before the swarm, straight away. The list covers files git already tracks; a new file the swarm hasn't added to git doesn't appear in it.
  • Tests. The test command is filled in for you (from the project's package.json and lockfile, falling back to npm test). Edit it if you need to, then click Run tests. The command must start with a known runner such as npm, pnpm, pytest, cargo, go or make, and can't use shell operators like && or pipes. While the tests are failing, Commit stays disabled.
  • Commit. Termpolis suggests a commit message. Commit undoes the hunks you rejected, then commits everything that's left in the folder, including new files the panel doesn't list.
  • Revert all. After you confirm, resets every file git tracks to the commit the swarm started from. Any uncommitted change to those files is lost, yours included. A new file that was never added to git stays where it is.
  • Refine. Not happy with the result? Describe what to fix and click Refine. The Start Swarm wizard opens with your note and the previous swarm's summary filled in, ready to launch a follow-up swarm.

25. Persistent Memory — the growing brain

A local, cross-agent memory store that never forgets and feeds itself, so every agent can semantically recall past work instead of you re-explaining context each session.

What's in it

  • Past AI conversations — Claude Code (~/.claude/projects/**), Codex (~/.codex/sessions/**), and Gemini (~/.gemini/tmp/**) transcripts are parsed, noise-stripped (tool calls / reasoning / system prompts removed), chunked, and embedded. Two things are left out on the way: the <project-memory> block a headless run (termpolis-cli exec, agent_exec) opens with, which is memory recalled from this store and would otherwise be stored again, and any session another computer started here over Linked machines, which is that computer's work, not this one's. Settings → Learn from conversations (on by default) turns this reading off completely, for example to rule it out while you chase a slowdown: nothing is deleted, search keeps working, and turning it back on picks up where it left off.

  • Your repo's code — git-tracked files (so node_modules/dist are excluded), chunked by line-window. The indexer reuses the same sensitive-file denylist as the read watcher, so .env, keys, and cloud credentials are never embedded.

  • Claude Code's saved notes (1.55.0) — the small markdown notes Claude Code keeps about each project in ~/.claude/projects/<folder>/memory/*.md. Left in place they are visible only to Claude, and only in that project. Once you have connected your agents (see Connecting your coding agents) and Include Claude Code's saved notes in memory is on (Settings → Memory & Learning; on by default), the background indexer reads them and keeps a copy in memory under the project they belong to, so Codex and the Antigravity CLI find what Claude knew. See Claude Code's saved notes below for exactly what is read, what is written and how to undo it.

How it works

  • Embeddings are local & offline. A bundled bge-small-en-v1.5 model (q8, 384-dim, MIT) runs in-process via onnxruntime-web (WASM) — no Ollama, no server, and zero native binaries in the installer. If the model is absent, search degrades gracefully to keyword matching.

  • Shared across all three agents over the MCP server (memory_search / memory_write / memory_list), once you connect them (see Connecting your coding agents). One store backs Claude/Codex/Gemini, so a fact one learns is instantly available to the others.

  • Durable across restarts, updates, and reinstalls. Stored as JSONL in Termpolis's app-data folder — %APPDATA%\termpolis\swarm-memory.jsonl on Windows, ~/Library/Application Support/termpolis/ on macOS, ~/.config/termpolis/ on Linux — and reloaded with embeddings at startup. When an OS keychain is available, each line is encrypted at rest (AES-256-GCM) with a random per-device key that the keychain protects; without one, the file stays plain text. Aged memories that idle consolidation moves to the cold archive (swarm-memory.archive.jsonl, in the same folder) are written under the same key; entries an earlier version archived stay plain text for now. The link graph (memory-graph.jsonl) and the corrections log (memory-corrections.jsonl, which holds the text of any amended memory and why) are not encrypted. Because it lives in your user profile, not the install folder, it survives app updates and even an uninstall/reinstall (the uninstaller leaves app data in place). A hot window of up to 500k chunks is kept in RAM for vector search; the on-disk log retains everything written.

  • The memory key is never overwritten or deleted. The key lives in memory-sync.key in the same folder, wrapped by the OS keychain. Termpolis creates one only when there is none, and never writes over an existing one. If this computer can't open it (the keychain isn't available, a password reset or a moved profile changed what the keychain can unwrap, or the file is damaged), memory comes up locked instead of starting over:

    • nothing new is saved, and agents get a clear error rather than a write into a store they can't read;
    • the background indexer, reflection and consolidation pause, and they pick up where they stopped once memory unlocks;
    • the Memory panel says why it is locked.

    If the key comes back, restart Termpolis and every memory returns. If it won't, click Start a new memory key in the Memory panel. The old key file is renamed aside (memory-sync.key.retired-<time>), never deleted, and a new key is created so memory saves again. A new key needs the OS keychain, because it is never saved in the clear: with none, Termpolis says so and changes nothing. Older memories stay on disk, unreadable until the old key works again; Termpolis tries every retired key at every launch, so they come back by themselves if it ever does.

  • Turning encryption off is all-or-nothing. It decrypts every file of the store (including rotated swarm-memory.<n>.jsonl generations and the archive), reads the result back to check it, and only then retires the key. If any step fails, nothing changes.

  • Backing up an encrypted brain. Copy memory-sync.key along with the swarm-memory*.jsonl files. The key is wrapped by the OS keychain, so it opens only under the same OS user account; on Windows the wrap also depends on the Local State file in the same folder, so back that up too. A backup without them can't be decrypted.

  • Feeds itself. A background indexer runs ~10 s after launch and every 30 min, ingesting new sessions. Ingestion is idempotent (content-hash dedup), so steady-state runs only embed genuinely new chunks.

  • Learning from an agent session stays light. When an agent terminal goes quiet, Termpolis looks at that session's transcript to learn from it. It first checks whether the transcript changed at all (a size and time check that opens nothing), then reads only what was added since the last look, and it runs one such look at a time for each repo and agent however many terminals are open on it. The first look at a transcript reads it in small pieces so typing is never held up; after that a long transcript costs no more than a short one (TERMPOLIS_REFLECT_FAST=off restores the old read of the whole transcript).

  • A local record of what memory did. memory_audit lists the latest things the brain did: memories stored (their kind and which agent stored them), recalls (the agent filter the search named, if any, how many memories it found and the ids of the top few), context digests handed to agents (their approximate size) and learning passes (their kind and a one-line note: for a commit or test run, whether it passed and how many memories it touched, never which project). Two files in the app-data folder hold it: memory-audit.jsonl, written by the process the memory store runs in, and memory-audit-main.jsonl, written by the main process. Each is capped at about 2 MB with one earlier copy kept (.prev.jsonl), and memory_audit reads both. Through 1.54 the main process never started its side of the record, so in the usual setup the tool listed nothing, and the recalls and digests an agent caused were never written down. The text of what an agent searched for is never recorded. The file keeps a clipped preview of each stored memory, with anything that might be a credential taken out, except for a lesson held back as a candidate and a note copied from Claude Code's folders, which get none. memory_audit leaves the previews out too: an agent is told what was stored and by which agent, never the words. It stays on this computer. Settings → Memory & Learning → Activity record shows how big it is and has a Clear activity record button (it asks first, removes those four files and nothing else, and leaves every memory alone); the record has no switch.

  • Pre-context primer. memory:build-primer pulls the most relevant memories for a query and formats a shell-paste-safe block that can be injected as an agent's first input — so it starts already knowing the context (the token-saver). With Auto-recall context on agent launch on (Settings → General; on by default), every agent you launch is pointed at it: Claude through its system prompt, Codex and Gemini through a one-line pointer typed into their input. The pointer waits while the agent's screen shows something waiting for an answer, such as a trust or approval prompt, or can't be read, so its Enter never answers a prompt for you. If you connected Claude Code with the optional SessionStart hook, sessions you start outside Termpolis load your project memory too.

  • Current-directory precedence. The primer leads with context for the project you're standing in — Claude Code's saved notes from this repo first (up to three passages, see below), then up to two lessons that say something a later reader can use (labelled lesson; see A lesson has to say something to be kept below), then past conversations from this repo, then its code/notes — and anything from other projects is appended under a "may NOT apply" label. Notes and lessons lead when they match what is asked: they are ordered ahead of the conversations they compete with, but each still has to be among the digest's best matches to be there at all. Ingested chunks are tagged with their project (derived from the transcript cwd / repo root), legacy chunks get back-tagged on the next indexer pass, and memory_search accepts a project filter so agents can scope recall themselves.

  • What fills the slots. The digest keeps the same number of lines and the same snippet length whatever leads it, so the order costs next to no extra tokens (the title of the project section is up to about thirty characters longer when saved notes or lessons lead it), and where there are no saved notes and no such lessons the order is the one it always was: the digest is ordered and cut exactly as in 1.54. What it holds can still differ from 1.54's where the sentences below say so (a chunk and the fuller chunk that begins with it become one line, the freshness lane leaves some memories out, a correction applies). A saved note leads unless someone has demoted it, and a lesson leads only if its text scores at the keep bar of the gate that decides which lessons are stored, nobody has demoted it, and it is this project's own, meaning filed under it, or filed under no project (an older store) and naming it (a demoted one is still shown, after the conversations, and so is a lesson filed under another project that merely names this one, which the mention brings in as it did before); a fourth note passage (a long note is stored in several) or a third lesson ranks with the conversations on relevance. Up to three slots show Most recent activity, the newest memories of the last seven days whatever their relevance, and that block gives a saved note one line however many passages it was stored in, and leaves out what a search leaves out: a memory a newer one supersedes, one whose feedback has turned it down, and one you or an agent retracted with memory_correct; it also leaves out one that was demoted or amended (a list in time order has no rank to lower, so the block does not list such a memory; the same memory still reaches the digest through relevance, where the correction applies), memories filed under a scratch folder (tmp, temp) or the placeholder cwd, unless that is the project the digest is about, and any saved note that is not this project's: another project's, and one written from a session in the home folder, which belongs to no project (a digest that names no project has no saved note to show, and one launched from the home folder itself shows that folder's own). When a chunk and the fuller chunk that begins with it both match, the digest carries the fuller one, and keeps the shorter one when there is no room left to show the fuller; across the project's own section and the "may NOT apply" one a passage is said once, by the project's row, even when that is the shorter of the two. A search whose best matches are other projects' saved notes, or lessons the gate holds back, is read again for more (up to 100 candidates, and only when it came back full), so what the digest must not show takes no place from what it may. Every digest (the Memory panel's preview, the check made when an agent launches, the one a termpolis exec run starts with, and the one memory_primer returns) searches through the same corrections as memory_search, so a retracted memory is left out of all of them and a demoted lesson never leads.

What memory can and can't tell an agent

  • Recall returns excerpts. A past conversation is stored in pieces of about 2,000 characters, so a search hit can start or end mid-sentence. Pieces from one session are usually linked in order: memory_graph with the hit's id and relation follows reads on, and precedes reads back. If reading on stops early, search again. The primer tells agents this.
  • Search ranks by similarity, and rarely comes back empty. Even a search with no relevant memory usually returns its closest matches with high-looking scores, so judge a hit by what it says, not by its score. To see the latest entries in order, use memory_list with your project instead.
  • Memory records what was said, not what is true now. A note that a disk was 48% full was true when it was written. Agents are told to verify anything they rely on.
  • Each computer has its own memory. Something you did or discussed on another machine isn't in this one's memory, and something that happened without an agent session (plugging in a drive, say) isn't in any.
  • Lessons are extracted automatically, and the extractor can be wrong. Termpolis reads finished sessions for problems that got fixed, decisions and gotchas. Through 1.50.0 it read many ordinary sentences as problems and decisions; 1.50.1 tightens its rules and, once, demotes the lessons it wrote by mistake, so they rank lower in search. A demoted lesson is still stored and still returned, ranked lower. Correct a bad lesson as it surfaces with memory_correct; nothing is deleted.
  • A lesson has to say something to be kept (1.55.0). Before a lesson is stored, Termpolis scores its text: whether it names something you could search for (a file, a function, an error code), says why or when, or is only narration such as "Root cause found.", "Next:" or a progress note. A lesson that says something is stored as before, and its importance now follows that score instead of the extractor's guess. One on the borderline is stored as a candidate (a lesson Termpolis stored itself; a note you or an agent tag candidate is shown as usual): it shows in the Memory panel, but no agent is ever given its words: not through search, the primer, listings, related or graph walks, memory_pool, memory_conflicts, memory_anticipate, code_locate or memory_audit (which says only that something was stored, by which agent, never what it said). The background passes that merge near-duplicates and write summaries leave it alone too, so its words cannot end up in a summary an agent can search. One that says nothing is not stored at all. Termpolis counts how many it did not store in memory-metrics.jsonl. The score reads the lesson's text and nothing else, and the words it listens for (a reason such as "because" or "never", and, in a lesson that also names something you could search for, a "so" or "since" that joins a cause to what follows, unless the sentence before it reports what happened, or, for a "since" that opens its sentence, the clause it opens does ("The lessonSubstance tests passed on the branch, so the gate is done.", "The lint job in ci.yml has been red since the bump landed." and "Since the main build passed, the release branch can be created." say nothing a later reader can use and are held back as candidates, while "The watcher in watchFolder.ts fires twice, so debounce the handler." is kept), or the lesson carries a progress note such as "today" or "right now") are English: a lesson in another language that names nothing you could search for counts as saying nothing. It is a heuristic, and it will sometimes refuse a lesson a person would have kept: one whose only reason is a "so" or a "since" is not stored at all when it names nothing you could search for. It cannot tell a status line in the present tense ("CI is green on the release branch, so the tag can be created.") from a rule, and a decision written in the past tense ("We decided to use pnpm since the lockfile is smaller and the installs are faster.") is held back with the reports: it is stored as a candidate and not shown, never deleted. Lessons stored before 1.55.0 are re-checked once on each computer by the same score (a second pass, after the 1.50.1 one, with its own marker file mneme-lesson-recheck-v2.json); the ones it would now refuse are demoted, exactly as in 1.50.1, and nothing is deleted. Borderline lessons stored earlier are left as they are. The score is a heuristic, so a demotion can be wrong: ask an agent to correct the lesson with memory_correct (an amendment replaces the demotion, and the lesson is back at full rank), or retract it.
  • memory_pool and memory_conflicts give each lesson's id. A pooled lesson lists the ids of every lesson that fed it (ids) and the id of the one whose wording it shows (id); each side of a conflict has its id. That is what memory_link needs for the step the conflicts tool suggests: record the winner and mark the loser with a supersedes link. A superseded memory stops coming back from search.

Claude Code's saved notes

Claude Code keeps what it learns about a project as small markdown notes, one fact to a file, in ~/.claude/projects/<project folder>/memory/. They stay private to Claude, in that one project: a search over past conversations only finds an echo of them, and Codex and the Antigravity CLI never see them. Termpolis can bring them into memory.

  • What is read. Only while you have connected your agents and Include Claude Code's saved notes in memory is on (Settings → Memory & Learning; on by default). An install that an older version connected, and that you have not yet been asked about (the one-time review), does not count as connected for this: it starts when you answer yes. One that you had connected with a yes before 1.55.0 is not asked again: it starts with the first pass after the update (about ten seconds after Termpolis starts, while memory is unlocked), and turning the switch off stops it. A pass runs with each 30-minute indexer pass (never the 90-second fast one), and straight away when you turn the switch on, and it stops at once, before the next note, if you turn the switch off or take the connection back while it runs. For each project folder it reads the names, sizes and modified times of the memory/*.md files, and the text of the new or changed ones. It skips MEMORY.md (the index of the others), files over 64 KB, binary files, links that lead outside ~/.claude/projects, and anything past 2,000 files. To learn which project a folder belongs to it lists that folder's session files with their modified times (at most 5,000 of them), reads the first 256 KB of the three newest and keeps only the working directory they record. For a project folder it has read notes from before, and for no other, a pass that finds its memory folder gone also lists the names of the folder's other entries, to tell a deleted notes folder from one it cannot read. A folder with no session yet is left out and counted ("N notes were left out" in Settings) until one appears.
  • What is written. A copy of each note in memory, split at paragraph breaks into chunks of at most 1,800 characters (about the embedder's window), each starting with the note's name and description so a chunk stands on its own. A copy is stored under the project's own name with agent and source claude-notes; kind fact (types reference and user), decision (feedback) or note (project, or no type), never message, so the idle clean-up that forgets cold conversation chunks leaves notes alone; importance 0.9 (0.8 for project and untyped notes); tags type:<type> and note:<file>; and the note's last edit as its date. The same at-rest secret redaction as every other memory runs first, and Settings counts what it redacted. Nothing is written into Claude's folders. A record on this computer only, claude-notes-manifest.json in the app-data folder, makes each pass incremental: a note that did not change is not even opened.
  • Staying true. Edit a note and only the chunks whose text changed are replaced; delete or rename it and its copy goes, and so does the copy of every note of a project whose whole memory folder you delete (the project and its sessions stay). Anything that could not be checked (a drive that is not mounted, a folder that could not be listed, a memory entry that cannot be followed) removes nothing. A pass that is interrupted is finished by the next one, which also clears any copy it can no longer account for, so copies never double up; that holds when the record on this computer is lost, even for hundreds of notes with one modified time, because the copies it cannot account for are removed before the new ones are stored. If the store itself is replaced under the record (a new memory key started from the Memory panel, the memory log deleted, or one put back from a backup that held none of the notes), the next pass sees that the store holds no saved note at all, starts the record over and imports every note again; deleting every imported copy by hand in the Memory panel looks the same, so they are imported again (once: they stay). Deleting only some by hand is respected for as long as this computer's record exists, because the record knows those notes are imported; if the record is lost they come back with the rest, and until you edit a note Settings counts what was imported, which can be more than memory holds. A backup that already held some of the notes is not noticed: the notes it lacks stay out until you edit them, or use Remove imported notes and turn the import back on. The copies that come in with a brain import from another computer are not part of this computer's record. If it has imported before and its record is whole, they stay beside its own copies. If its record is new, lost, or was left unfinished by an interrupted pass, its next pass removes every copy it cannot account for, those included, and imports its own from the notes on this computer (turn the import off first to keep them, until you turn it on again). Clearing all of memory empties the store, the copies with the rest, and Settings stops counting them; it does not bring them back (a note you edit afterwards is imported afresh).
  • Who gets them. Every connected agent, through memory_search, memory_list and memory_primer, in that same project: an agent has to name the project (its working directory, as the tools' project and cwd) for the search, the listing or the digest to find them. The launch primer lists a project's saved notes first (up to three passages, labelled note, with the age of the last edit), then lessons and past conversations; another project's notes never appear in it. Notes carry the memory type summary, so memory_pool and memory_conflicts, which compare lessons, leave them out, and the idle consolidation never merges one into another memory or copies its words into a summary.
  • Across projects (off by default). A project's reference, feedback and user notes (how you like to work, where things are) are as true in the next project. Let general notes help in other projects lets a memory_search scoped to one project also return up to two of them from another, after that project's own results, still marked with the project they came from, only when they match at least 0.5 and never beyond the search's limit. memory_anticipate, which names no project, returns them under the same switch and otherwise none, and so do a memory_search or memory_list that names no project and a walk of the graph (memory_related, memory_graph): such a call cannot say whose notes it may have, so it gets no saved note unless you turn this on, and then only the general kinds. The rest of memory those calls cover as before. Notes about one project's own state (project) never leave it. Notes from sessions you ran in your home folder belong to no project, so a search that names a project is never lent them, whatever this switch says.
  • Staleness. A note is what Claude believed when it wrote it. The primer shows its age, and, like all memory, it is to be verified before it is relied on.
  • Undo. Remove imported notes (Settings → Memory & Learning) asks first, then removes every saved-note copy in this computer's memory (the ones a brain import brought in as well), turns the import off and says how many went. Claude's own notes are untouched. Turning the switch off only stops the reading; the copies stay in use until removed. A locked memory pauses the import like every other writer, and the panel says when memory is locked; if it is locked when you remove, the copies it could not remove are listed as waiting, and the next indexer pass takes them out once memory is open, with the import still off. Both switches are saved when you change them; if Termpolis cannot save one it says so and leaves it as it was (Remove imported notes then removes nothing, and says so), and if it finds its settings file unreadable it leaves the import off until you turn it on. When memory cannot be searched all the way through at that moment (the memory process is busy, say), a copy this computer had lost track of may be missed: the panel says so, and choosing Remove imported notes again finds it.

Using the Memory panel

Open the panel with Ctrl+Shift+M, or from Settings → General → Open the Memory panel. From there you can:

  • See what's stored — the number of remembered chunks (and how many sit in the in-RAM hot window for fast search).
  • Search — type what you're working on and hit Search for a semantic lookup across your past conversations and indexed code.
  • Inject primer — type a topic and click Inject primer to paste the most relevant memories straight into the active agent's terminal, so it starts already knowing the context. This is the token-saver: you stop re-explaining background every session.
  • Index this repo's code — pull the current project's git-tracked files into memory on demand (.env/keys are always skipped). Conversations index themselves automatically; code indexing is opt-in per repo so you decide what's searchable.
  • Sync across machines — unavailable in this version. The earlier design (a shared synced folder) could lock or overwrite a computer's memory, so it is switched off while it is rebuilt: the panel shows it as unavailable, and Termpolis refuses to turn it on. If you had it on, turning it off simply reopens this computer's own memory as it was left. Each computer keeps its own memory. To use what another of your computers knows, link the two with Linked machines.
  • If memory is locked, a banner at the top of the panel says why and offers Start a new memory key (see How it works). It asks before doing anything.

Why it matters: when Claude figures out how your auth module works, Codex doesn't need to re-discover it, and you stop burning 20–50k tokens re-pasting context every session. The store is JSONL in your app-data folder (see above); without an OS keychain it stays plain text, readable and hand-editable.


26. Observability

Termpolis ships with a full observability stack for AI work — the "watchers" system. It's a lightweight in-process event bus that watches for:

  • Token pressure — when an agent is approaching compaction.
  • Stuck sessions — when an agent has been silent for > N seconds.
  • Error cascades — repeated errors in a short window.
  • Redundancy — two agents doing overlapping work.
  • Efficiency — the token-cost-per-task rolling average.

Watchers can surface alerts in the status bar, in the activity feed, or fire a system notification. Thresholds are tunable in settings.


27. Status Bar

Status bar

The bottom strip shows, left to right:

  • Active workspace + git branch (click to switch).
  • Focused terminal's shell type + cwd.
  • Active agent summary — how many are working, how many idle.
  • Swarm status — if a run is active, shows progress %.
  • Token counter — session total across all agents.
  • Notifications — watcher alerts live here.
  • MCP server indicator — green when healthy.

28. Troubleshooting

Found a bug that isn't here? Open an issue on GitHub → Include your OS + version, the Termpolis version (shown at the top of Settings), and the most recent entries from the app log (Ctrl+Shift+O, or app.log in your data directory) — that's usually enough to reproduce the problem.

Installation & first-run

Windows: "Windows protected your PC" SmartScreen warning. Click More info → Run anyway. Termpolis is code-signed (SSL.com), but newly signed builds need reputation time before SmartScreen stops flagging them. The warning disappears once enough people download the release.

macOS: "Termpolis is damaged and can't be opened." This means Gatekeeper couldn't verify the signature — usually a partial download. Re-download the DMG from GitHub Releases, verify the file size matches, and mount again. If it still fails, open System Settings → Privacy & Security, scroll to the bottom, and click Open Anyway next to the Termpolis entry.

macOS: "Permission denied" when launching a terminal. Grant Termpolis Full Disk Access in System Settings → Privacy & Security → Full Disk Access. Re-launch after granting.

Linux: AppImage won't run. Mark it executable: chmod +x Termpolis-*.AppImage. On systems with hardened FUSE, extract and run the inner binary: ./Termpolis-*.AppImage --appimage-extract && ./squashfs-root/termpolis. If the window never appears and the output ends with Unable to access(W_OK|X_OK) /dev/shm: No such process, update to v1.51.5 or later; on an older version, start it with --no-sandbox.

Data directory didn't appear. Termpolis creates the data directory on first run — make sure you actually clicked "Open" rather than dismissing the first-launch dialog. Paths by platform: %APPDATA%\termpolis\ (Windows), ~/Library/Application Support/termpolis/ (macOS), ~/.config/termpolis/ (Linux).

Terminals

Terminal won't start. Check which shell it uses: Settings → General → Default Shell for new terminals, or the Shell field in the New Terminal dialog. Termpolis offers only the shells it finds at their standard install paths; on Windows, PowerShell 7 lives at C:\Program Files\PowerShell\7\pwsh.exe. Press Ctrl+Shift+O to open the app log and see why the terminal failed to spawn. On macOS, if /bin/zsh gives "permission denied", re-grant Termpolis Full Disk Access (above) — launchd blocks unsigned/unapproved apps from spawning shells by default.

Terminal hangs on first prompt. Your shell's startup files (.bashrc, .zshrc, powershell $PROFILE) may be waiting on input or hitting a slow network check. Open the shell outside Termpolis to confirm; the fix is in your dotfiles, not the app.

Output looks garbled / escape codes show as text. On macOS and Linux, Termpolis starts every terminal with TERM=xterm-256color, so a program that prints raw escape codes has usually been told otherwise. Check whether your shell's startup files (.bashrc, .zshrc, PowerShell $PROFILE) set TERM to something like dumb, and open a fresh terminal from the New Terminal dialog to confirm.

Copy/paste shortcuts don't work. On Windows/Linux, Ctrl+C copies only when text is selected; with nothing selected it interrupts the running program. Ctrl+Shift+C always copies, and Ctrl+V or Ctrl+Shift+V pastes. On macOS, ⌘C/⌘V copy and paste, and Ctrl+C always interrupts.

Font looks wrong / icons are boxes. The app ships with its own icon font. If a terminal's text looks wrong, check the font family and size in Settings → General → Terminal Defaults, or right-click a terminal in the sidebar to change them for that terminal alone. If the icons in the app itself show as boxes, the bundled font failed to load: restart Termpolis, and reinstall if it keeps happening.

Agents & CLI tools

Agent launch button fails silently. The CLI isn't on your PATH. Open any shell in Termpolis and run claude --version (or codex, gemini) to confirm. On macOS, GUI-launched apps don't always inherit $PATH from your shell — restart Termpolis after updating ~/.zprofile (not just ~/.zshrc), or relaunch from Terminal with open -a Termpolis so the shell PATH is inherited.

Wrong claude / codex binary runs. If you've installed the CLI via multiple package managers (Homebrew, npm, cargo), PATH order decides the winner. Use which claude to see which one Termpolis will launch. To pin a specific one, add a custom profile with the + on the sidebar's AI Agents section and give it the full path as its command.

An agent can't run sudo (Linux and macOS). An agent runs its commands without a terminal, so plain sudo has nowhere to ask for your password. Every Termpolis terminal sets SUDO_ASKPASS, so sudo -A there asks through a Termpolis password dialog that shows the whole command about to run. Termpolis tells Codex, and Claude Code in projects with saved memory, to use it; for other agents, mention sudo -A in your prompt. Type your password only for a command you expected, because whatever you approve runs as administrator. The helper behind the dialog (askpass/sudo-askpass in the data directory, set as SUDO_ASKPASS in every terminal) won't run unless sudo started it, and won't ask at all for a command too long to show in full. It can't protect you from a program that is already hostile and running as you, which could show a lookalike dialog of its own. A SUDO_ASKPASS you set yourself is left alone. On Linux the dialog uses zenity, kdialog or ssh-askpass, tried in that order; with none of them installed, sudo -A fails with a message saying so. On macOS it uses the system dialog.

sudo fails in every terminal with sudo: The "no new privileges" flag is set (Linux). In-app updates of the .deb from Termpolis 1.50.0 and earlier reopened the app in a way Linux marks "no new privileges", which stops sudo, su and pkexec in every terminal it opens. Termpolis shows a notice when it starts in that state and, where systemd-run is available, offers to restart itself properly. You can also quit Termpolis and open it again from your applications menu. Later in-app updates no longer cause it.

Agent exits with "API key not set". Each agent's env vars come from the login shell, not from a .env file in your workspace. export ANTHROPIC_API_KEY=... in ~/.zprofile / ~/.bash_profile / PowerShell $PROFILE, then relaunch Termpolis.

Swarm, MCP, and memory

Agents can't reach the MCP server. The server may have failed to start. Open the app log (Ctrl+Shift+O) and look for the lines about the MCP server's port. Common causes:

  • No free port. The server tries port 9315, then the next four (up to 9319). If all five are taken — usually by stray Termpolis processes left behind by a crash — it can't start. Kill any stray termpolis processes and relaunch.
  • Firewall blocking localhost. Rare but possible. Add an exception for termpolis.exe / the Termpolis binary.
  • Token file write failed. mcp-token in the data directory (see First run) couldn't be written due to permissions; the app log says [mcp-token] could not write. The rest of Termpolis still starts. Termpolis first grants itself back into a token file it locked itself out of, then replaces it with a fresh one, which also drops any other account the old permissions let in (versions up to 1.52 could do that on Windows when the account name had a space or an accent, or when a local account had the same name), so if this persists, fix that folder's permissions so your user account can write to it.

An agent doesn't see Termpolis's tools or memory. Since v1.49, Termpolis registers itself with Claude Code, Codex and Gemini CLI only after you connect them. Check Settings → Agent Integration, connect if needed, then start a new agent session.

Swarm conductor doesn't launch. The conductor runs Claude Code in a hidden terminal, so it needs claude installed (see agent troubleshooting above). If it exits with an error or before creating a task, the swarm ends and the Swarm Dashboard's Messages tab shows Conductor exited (code N): with the last lines it printed (see When the conductor exits). Press Debug in the dashboard to read that terminal's output, startup included, without leaving the dashboard.

Swarm hangs mid-task / agents stop posting activity. Open Activity Feed — if the agent is still running but not emitting events, its MCP connection may have dropped. Use the intervention controls in that agent's Activity Feed to recover: Interrupt stops what it is doing, and Steer gives it a new instruction (see §23). If a specific agent keeps dropping, restart Termpolis; the MCP server gets a fresh token at each launch.

Memory search returns nothing. Embeddings now run in-process via a bundled offline model (bge-small-en-v1.5) — no Ollama or any server required. If semantic results are missing, the embedding model failed to load on this machine; keyword-only matching still works as a fallback, and writes always succeed.

Accounts

A switch typed nothing. Termpolis types only what it can see the agent is ready for. When it can't tell whether the agent is still running, or the agent didn't show that it exited, it stops, and the card says so. Look at the terminal: once it shows your shell's prompt, press It has exited, switch now. Where the operating system itself still shows a program running in the terminal, the card says so, and that button is for a terminal at its prompt: when nothing on the screen shows the shell is back (the agent's box is still drawn, or a dialog of its own that Termpolis doesn't know), Termpolis types the first part of the line and looks where it landed before it presses anything else. It takes that back out with Backspaces, never with a key that would stop a turn at work, close a menu or answer a question: out of an agent's box, a draft you left there goes with it and is never submitted; where the line shows nowhere in time, nothing more is pressed and the card says the line never showed; where it lands at the shell, the line is cleared (text you left on it goes too) and the switch goes on as usual. Claude Code's own questions on a resumed session (how to resume an old, large one; trusting a folder; a new MCP server) are yours to answer in the terminal, and the switch stops there at once. While the agent works on a turn, the switch waits for the turn to end instead.

" can't see this conversation." For Claude Code, that account's projects folder isn't shared with this one's, and the transcript couldn't be copied there; for Codex, its sessions folder isn't shared. Press Start a new session on (Codex starts one by itself); the old conversation is still in Past AI Sessions. A new account shares those folders unless you untick them under Advanced when you add it.

"The account line never showed in the terminal." The shell didn't show what Termpolis typed (it may have closed, or still be busy), so nothing was entered. Once the terminal shows its prompt, press Try again.

A switch in a Git Bash terminal takes longer. Git Bash reads what is typed one character at a time, about 40 ms each and 100 ms and more while the computer is busy, and draws the line only once it has read all of it. Termpolis types the account line in two parts, waits for the first part to show, and gives the rest the time the first part took, twice over, before it gives up; it enters nothing until the whole line shows. A long line can take 5 to 15 seconds in Git Bash and well under a second in PowerShell or cmd. That is the shell's pace, not a stuck switch.

The account line starts with &, or uses call. That is on purpose. Each kind of shell gets a form that runs nothing in the others: & { … } in PowerShell, set "…" && call … in cmd, export … && … in bash and zsh. A shell started inside the terminal's own then makes the line fail, instead of starting the agent on the old account. cmd can't take a profile folder whose name holds %, ! or "; the switch says so and types nothing.

Two windows opened when an agent ran out of usage. A StopFailure hook in your Claude Code settings opened one, besides Termpolis's card. See Your StopFailure hook.

An expired sign-in no longer opens a window on another account. Once your StopFailure hook skips inside the terminals Termpolis watches, it skips there for an expired sign-in as for a usage limit, and Termpolis's card opens only for a usage limit. Sign the account in again from its card or its menu.

The switch says the account's sign-in has expired. The agent started on that account showed its sign-in screen: the sign-in stored there no longer works, though the agent's status still said it was signed in. Termpolis answers nothing on that screen and marks the account Sign-in expired. Press Sign in to .

Check says a Termpolis terminal is running on the account. Termpolis doesn't ask an agent who is signed in while one of its terminals runs on that profile, because the check could refresh the sign-in that terminal uses. Close the terminal and press Check again, or go by the email the card shows.

My status line shows my default account in a profile Termpolis made. Your ~/.claude/accounts.sh doesn't list that profile yet, so the scripts that read it take it for your default account. The Add account sheet's last step shows the lines that add it, with Copy; Termpolis never changes that file itself.

Updates & performance

Update notification appears but the update doesn't install. The auto-updater needs write access to the app bundle. On Windows, run the installer manually from GitHub Releases if the in-app updater fails. On macOS, drag the new DMG contents over the existing app (it'll prompt for admin). On Linux, download and replace the AppImage, or see below for the .deb.

How an update of the Linux .deb installs (1.53.1 and later). When the banner says an update is ready, Restart now closes Termpolis at once. Your desktop then asks for your administrator password, once, in its own dialog ("Authentication is needed to run `/bin/sh' as the super user"), and installs the downloaded package. Termpolis opens again by itself: the new version, or the one you had if nothing was installed. Quitting Termpolis never installs the update and never asks for your password, so Restart now is the only way in, and the banner keeps offering it until you use it. When Termpolis opens again without the new version, the banner says why:

  • "Update not installed: the administrator prompt was cancelled." You closed the password dialog. Restart now comes back with the next update check, a few seconds after Termpolis opens, or press Check for updates in Settings.
  • "…couldn't get administrator rights to install it…" No password dialog could be shown (no polkit authentication agent is running, or the system said no). The banner gives the command to run in a terminal instead, sudo apt install and the downloaded file.
  • "Update not installed." followed by an error. dpkg or apt-get failed, and that is their error. The full output is in update-install.log in the data directory. If Termpolis was still running two minutes after Restart (stuck while closing), nothing was installed, and the error says so: press Restart now again.

When the new version does open but dpkg or apt-get reported a problem on the way, the banner says so ("Termpolis 1.54.0 is installed, but the installer reported a problem"), with their error. A package Termpolis needs that can't be installed is one such problem: apt-get is told never to remove a package to settle it, so Termpolis stays installed, and the error names what is missing.

On a system without pkexec, Termpolis asks through sudo -A and its own password dialog (zenity, kdialog or ssh-askpass), which shows the whole command. With neither, the banner gives the command to run. If you open Termpolis yourself while the update is being installed, it says so and closes again when you click OK, and Termpolis opens by itself once the install has finished. (Leave that message up until then and it can't: open Termpolis from the menu.)

Updating from 1.53.0 to 1.53.1 (Linux .deb). This one update still runs 1.53.0's installer, which waits for the install inside the window. When you click Restart now, Termpolis stops responding while the password dialog is up and the install runs, and your desktop may say it is not responding: click Wait, and Termpolis restarts on 1.53.1 once the install has finished. (After Force Quit, open Termpolis again: the install usually finished anyway.) If you cancel the password dialog, a second one follows, because 1.53.0 then tries apt-get install -f: cancel that too and Termpolis stays on 1.53.0. With the update downloaded, quitting 1.53.0 also asks for your password, after the window has closed, with the same second dialog if you cancel; entering it installs 1.53.1, so give it a minute before you open Termpolis again. Or install 1.53.1 yourself: quit Termpolis (cancelling both dialogs, if it asks), download termpolis_1.53.1_amd64.deb from GitHub Releases, and run sudo apt install ./termpolis_1.53.1_amd64.deb in your desktop's terminal, in the folder you saved it to. Updates after 1.53.1 install as described above.

App is slow to start / very high memory. A corrupted session file occasionally causes runaway restoration. Back up session.json in your data directory, then delete it and relaunch — you lose restored workspace state but the app is back to a clean baseline.

Terminal scrollback is sluggish. The default xterm scrollback is 10,000 lines. If you've pasted very large logs, scrolling slows down. Press Ctrl+Shift+X (Clear Terminal) to wipe the active terminal's screen and scrollback without restarting it.

Typing while Termpolis works in the background (1.54.5 and later). What is guaranteed: typing never waits on background work. Starting processes, reading files and indexing stay off the thread that echoes your keys; bursts of terminal output and of file-watch events are rate-limited; background work (memory indexing, transcript ingest, file-watch restarts, git checks for workflow triggers, update checks) pauses while the app is busy and resumes when it is calm; the memory host never moves onto the typing thread, even when it fails to start; and Settings → Learn from conversations can be switched off. What is not guaranteed: the operating system or an antivirus scanner (such as Microsoft Defender) can still slow the whole machine, and Termpolis cannot reserve RAM for itself. The setting reads: "On by default. Termpolis reads your past Claude Code, Codex and Gemini conversations in the background and stores them as searchable memory. Turn it off to stop that reading completely, for example to rule it out while you are chasing a slowdown. Nothing is deleted and memory search keeps working; it just stops adding. Turning it back on picks up where it left off."

Every one of these mechanisms has a switch that puts back the 1.54.4 behaviour, for ruling it out while you chase a problem. Environment variables are read where Termpolis starts:

  • TERMPOLIS_NO_GOVERNOR=1: no busy watchdog; nothing ever pauses.
  • TERMPOLIS_NO_HOST_PRIORITY=1: the background hosts (memory, proc) keep normal process priority. A comma list (memory, proc) keeps it for the hosts it names alone. The Headroom proxy and the remote bridge always run at normal priority: they carry model replies and phone typing.
  • TERMPOLIS_CONSOLE_LIST_NORMAL=0: the console-list helper (the check before typing into an agent on Windows) stays at the below-normal priority it inherits from the proc host, instead of putting itself back to normal.
  • TERMPOLIS_CONSOLE_LIST_INPROC=1: on Windows, the check before typing into an agent and the closing of a terminal read the terminal's console the 1.54.4 way, with node-pty's own helper started from the main process, instead of the console-list helper run by the proc host.
  • TERMPOLIS_CODE_GRAPH_FILE_CAP=<KB>: the code graph leaves out a source file longer than this (default 512 KB; its parse is one block on the main thread). 0 turns the cap off.
  • TERMPOLIS_INGEST_UNAVAILABLE_SKIP=off: conversation learning and learning from an agent session run their passes while memory is unavailable (every write fails), instead of skipping them until memory is back. Either way, a session pass whose lessons memory refused leaves its place in the transcript, so the next pass learns those turns again.
  • TERMPOLIS_TYPING_TRACE=0: the keystroke-to-echo trace is off and adds nothing.
  • TERMPOLIS_MEMORY_INPROC_GUARD=off: the old in-process memory fallback, whatever the store size (TERMPOLIS_MEMORY_INPROC_MAX_MB moves the 256 MB limit instead).
  • TERMPOLIS_INGEST_FAST=off: conversation ingest reads whole files and the whole corpus each pass, as before. It also lets a drain re-read a transcript that is still growing on every follow-up, with no cap on the number of follow-ups (by default a drain reads a file once and stops after 400 follow-ups).
  • TERMPOLIS_REFLECT_FAST=off: learning from an agent session reads its whole transcript, as before.
  • TERMPOLIS_SCOPED_WATCH=0: one plain recursive file watch per repository again, with no storm limit.
  • TERMPOLIS_BUNDLED_TOOLS_SYNC_PROBE=1: a workflow step or run_and_wait terminal opened in the first seconds after start, before Termpolis has checked whether jq, yq and nano are installed, checks for them in the main process, instead of putting Termpolis's own copies first on that terminal's PATH.
  • TERMPOLIS_SYNC_WORKFLOW_LIST=1: the workflow list and triggers of the active terminal's project are read in the main process again on each cd and tab switch.
  • TERMPOLIS_DISABLE_RESIZE_FLUSH=1: no output flush before a terminal resize.
  • TERMPOLIS_TERMINAL_DATA_ROUTING=0: every terminal's output is sent to the window, shown or not.
  • TERMPOLIS_DISABLE_PHONE_RESYNC=1 and TERMPOLIS_DISABLE_FLATTENER_PARITY=1: the phone bridge as before.

Window switches are localStorage keys set to 0 (for example from the developer tools console), then reload the window: termpolis.outputChunks (the old output buffer, which also turns off the write scheduler), termpolis.writeScheduler (a separate output throttle per pane), termpolis.resizeOrder (fit and resize at once), termpolis.fitFloor (a pane's fit declines nothing, not even a collapsed pane's 2 x 1 grid), termpolis.resizeTellFirst (a resize behind a backlog tells the pty the new size when xterm's resize comes up, not at once, and the write scheduler no longer ends a slice at the resize's wait), termpolis.hiddenPanes (hidden panes keep rendering), termpolis.dataRouting (one output listener per pane) and termpolis.paneEffects (the per-chunk work runs for every chunk).

Session corruption & reset

App opens to a blank screen. Sign of a broken session.json. Close Termpolis, rename session.json in the data directory, relaunch — the app creates a fresh session. Your workspaces will be empty but the app is usable again; the old file is preserved if you want to diff it later.

Reset everything. Close Termpolis, delete the entire data directory (see §2), relaunch. This wipes workspaces, settings, themes, prompt templates, custom workflows, swarm history, and memory — start from a clean slate.

Reporting a bug

If none of the above fixes your problem, open an issue. Please include:

  1. OS + version (e.g., Windows 11 23H2, macOS 14.3, Ubuntu 22.04).
  2. Termpolis version (shown at the top of Settings).
  3. Steps to reproduce — as minimal as you can make them.
  4. The relevant tail of the app log: press Ctrl+Shift+O, or open app.log in the data directory (%APPDATA%\termpolis\ on Windows, ~/Library/Application Support/termpolis/ on macOS, ~/.config/termpolis/ on Linux). Swarm and MCP problems land in the same log.
  5. A screenshot or short screen recording if it's a UI bug.

29. Termpolis Remote (phone app)

Termpolis Remote is an iPhone app (on the App Store as Termpolis R) that shows the terminals running in Termpolis on your desktop and lets you type into them while you are away from the machine. It is a pass-through, not a second Termpolis: nothing runs on the phone — no agent, no memory, no embeddings, no API keys. The desktop keeps running the Claude/Codex/Gemini session it was already running, signed in the way it was already signed in; the phone sends keystrokes and receives output. Lose the phone and you have lost a display, not an account.

Remote is off by default. It shares the relay and one background process with Linked machines, but each has its own switch: a phone has no direct way to send work to a linked computer (an agent it types into still can), and a linked computer can't see your terminals. Switching either one on or off restarts that process, so the other's connections drop for a moment.

Relay access is a subscription in the phone app ($4.99 a month on the App Store, first week free for new subscribers). The desktop app stays free.

Turning it on

Settings → Remote.

  1. Tick Allow phones to connect. The desktop half runs in its own utilityProcess, off the main thread, so a stalled relay connection cannot slow the terminals down. If it crashes it is restarted; a fourth crash inside a minute turns Remote off rather than restarting forever, and the pane says so.
  2. Leave Relay address at wss://relay.termpolis.com unless you are running your own. The relay's source (Apache-2.0, like the rest of the repo) is in relay/ (a Cloudflare Worker plus a Durable Object), and pointing the setting at your own deployment is supported. The phone takes the address from the pairing QR code and only accepts wss://; phones paired before a change keep using the old relay and must pair again. The field takes a wss:// address only, or plain ws:// for a relay on this computer (localhost or 127.0.0.1): over plain ws:// to another computer, anyone on the network in between could see when your devices connect and take their place in the relay first. A plain ws:// address saved by a version before 1.53 is kept but no longer used. Nothing connects through it, and the Remote and Linked machines panes both say so until you save a wss:// address or click Use the default relay.
  3. Under Pair a device, type a label for the phone (it defaults to "Phone") and tick what it will be allowed to do. Only Read terminal output is ticked to begin with. Press Pair a device.
  4. A QR code appears with a countdown. It works once and is valid for 90 seconds; closing the dialog withdraws it. Scan it with Termpolis Remote. The desktop shows the code only as a picture, so the app needs camera access to pair.
  5. Both screens show the same eight words, derived from the two devices' long-term keys. If they match, nothing is sitting in the middle. If they differ, tap They do not match — unpair on the phone and Revoke the device on the desktop.

Treat the QR code as a password for its 90 seconds. Whoever scans it first is paired — including through a screen share or a recording — and gets the ticked permissions straight away: nothing waits for the words to be compared. Pair where nobody else can see the screen, and revoke any device you don't recognise.

What the phone is allowed to do

Each phone has four permissions, chosen when it is paired and changed at any time with the checkboxes in its row under Paired devices. They are checked on the desktop for every request, and a change takes effect at once. The phone only reports them (its Settings screen lists What the desktop allows) and hides the controls it isn't allowed to use.

Permission Default What it allows
Read terminal output on List the terminals saved in the desktop's session, i.e. the sidebar (name and working folder; swarm workers are excluded), and stream the output of the one the phone has open.
Start terminals off Start a terminal. The app offers Claude, Codex or Gemini (agy) in a folder picked by browsing the desktop's folders, and a plain shell. The picker opens at the desktop's home folder, which also lists every drive the desktop can see (C:\, D:\ and so on, mapped drives included; / on macOS and Linux). Network paths (\\server\share) are refused, both for browsing and as a terminal's folder. The terminal is created over MCP like a swarm worker's, so sanitizeAgentCommand launches it the same way: Claude and Gemini with --dangerously-skip-permissions, Codex with -a never -s workspace-write and its update check off.
Type into terminals off Send text to any open terminal, then Enter. This bypasses the command checks — it is a keyboard, and anything typed runs as you. Deliberately not implied by Start terminals.
Close terminals off Close a terminal. The current phone app has no control for this.

Using it

  • Terminal list — the terminals in the desktop's sidebar (from list_terminals, i.e. the saved session, so swarm workers are not in it), titled with the desktop's name; pull to refresh. While a terminal is open the phone shows its agent's status (Thinking, Working, Waiting for you, …) and a one-line summary; the list keeps the last status seen.
  • Output — streamed as it is printed, for the terminals the phone has open. The desktop flattens it through a headless terminal first, so cursor moves and redraws arrive as edits and colour survives. Opening a terminal brings back what its last 32,768 characters of raw output draw.
  • Send — types the text, then presses Enter 150 ms later; several lines go in as one bracketed paste. There are no special keys (Esc, Ctrl+C, Tab, arrows, or Enter on its own).
  • Up to 16 desktops on one phone, each with its own key pair; Desktops at the top left of the terminal list switches between them.
  • New AI terminal — the terminal is created over MCP, but the bridge tags the request with the phone's device id, so the renderer adds it as an ordinary AI terminal: in the sidebar, in the saved session (and so in the phone's list), and relaunched with its agent when a workspace holding it is restored. Main lists it from the moment it opens, until the session's debounced save records it. It still counts towards the eight-terminal MCP cap, and closing it on the desktop frees its slot.
  • Backgrounding disconnects the phone; it reconnects on return. The desktop keeps the newest 262,144 characters of output per phone meanwhile, dropping older output and marking the gap as skipped.
  • On the desktop, the title bar shows a phone icon while a phone is connected, and each device row shows connected / last seen, its four permissions, Show safety words and Revoke.

Unpairing and revoking

  • Revoke (click again on Really revoke?) deletes the device record and closes its connection at once. The phone isn't told; it sees the desktop as offline until it is paired again. Terminals it started keep running. Revoking and changing permissions need Allow phones to connect on.
  • Unpairing on the phone erases that desktop's key and works offline. The desktop is told only if it is the phone's current, reachable desktop; otherwise revoke it there too.
  • Unticking Allow phones to connect ends every session but keeps the pairings. A phone not seen for 30 days is forgotten.
  • Deleting the app is not a reliable unpair: iOS can keep Keychain items after an app is deleted.

How it is secured

  • End-to-end encrypted. X25519 key agreement, HKDF-SHA256 derivation and ChaCha20-Poly1305, with a key per direction and a counter in every sealed frame that rejects replayed or reordered frames. Every connection mixes in fresh ephemeral keys, so recorded traffic stays sealed even if a long-term key later leaks; the pairing exchange itself is sealed under the long-term keys only.
  • Pairing the relay can't step into. The desktop's public key and a one-time secret travel only in the QR code, so a relay that never saw it can't open or forge the pairing. The eight words are the check against a swapped code; they are stable for the life of the pairing.
  • The relay is not trusted. It sees both IP addresses, which side is which, connection times, a room id that is stable for the life of the pairing, the size and timing of every frame (frames are not padded) and the unencrypted frame headers (type, public keys during pairing and connection setup, a counter). It can drop or delay frames but not read or alter them. It stores no frames; Cloudflare's own logs keep request metadata.
  • Desktop-side checks. Every request is checked against the device's grants. Terminal actions then go to the desktop's own MCP server over localhost — limited to list_terminals, create_terminal, run_command, write_to_terminal and close_terminal, with the same rate limits and audit log (tool name and device id, not the text) as any MCP client. Folder browsing (any local drive, folder names only, never a network path) and the output stream are handled in the bridge and are not audited. The phone's own requests reach no memory, swarm or other tools; an agent it types into still has its own.
  • Keys. The desktop's identity key never reaches the renderer, and is encrypted with the OS keychain (DPAPI / Keychain / libsecret) where one is available. On Linux without a keyring it is stored unencrypted in the data directory, and Settings → Remote and → Linked machines both say so. If a keyring turns up later, restart Termpolis: Encrypt keys now in either pane then encrypts the keys saved without one (the identity key and the file of links this computer joined), and from then on they open only while the keyring does. remote-devices.json (labels, public keys, room ids, grants) is plain. The phone makes a new key pair per desktop and keeps it in the iOS Keychain, available only while the device is unlocked and never synced to another device.
  • A key the keychain can't open is never replaced. That happens when the keyring is locked or not running, after a password reset, or when the data folder came from another computer or user account. Nothing that needs that key starts, nothing is written over it, and the panes say why. Restart Termpolis once the keychain is back and every pairing returns. For a key that will never open, Start over renames the identity, the links file and remote-devices.json aside (<name>.retired-<time>, never deleted) and makes a new identity; every phone and linked computer then has to pair again. Versions before 1.53 replaced such a key without a word, which lost every pairing.

Privacy details are in PRIVACY.md; the combined policy for the desktop app, the phone app and the relay is at https://termpolis.com/privacy.html.


30. Linked machines

Linked machines pairs your Termpolis desktops with each other, the way a phone pairs with one. An agent on one computer can then have a Claude, Codex or Gemini agent on another do a task headlessly and get its final answer back as an ordinary tool result. It works in both directions and across any network:

"Have Claude on linux implement the parser in ~/repos/foo and commit it, then review the commit yourself."

Nothing is typed into a terminal and no terminal opens on either machine. The work runs as a background job, and both computers list it under Activity.

Linked machines is off by default. It uses the same relay and the same background process as Termpolis Remote, but the two switch on separately: turning on one never lets the other's devices connect.

Turning it on

Settings → Linked machines, on both computers.

  1. Tick Let this computer link with my other computers. The pane shows the relay in use, which you change under Settings → Remote. It has to be an encrypted wss:// address.
  2. On one computer, go to Link a computer. Choose whether the new computer may run read-only agents here (see below) and click Create code. A code starting termpolis-link: appears with Copy, a countdown and Cancel. It works once and expires after 5 minutes.
  3. Carry the code to the other computer any way you like: a chat message, a shared folder, or typing it. Paste it under Enter a code from another computer, choose whether the first computer may run read-only agents there, and click Link.
  4. Both screens show the same eight words, and each suggests the other computer's hostname as its name. Change the name to whatever you want to call that machine. Compare the words, then click They match — link on both computers. The words come from the two computers' keys, so they match only if nothing sits in the middle.

Each computer runs nothing for the other until you confirm the words on it. Your agent will not send work to a machine you have not confirmed, and a machine refuses work from one it has not confirmed.

Each linked computer gets a row in Linked computers:

  • an online dot;
  • its name, which you can rename by clicking it (Enter saves, Escape cancels);
  • waiting for confirmation, until you confirm it;
  • its two permissions, and a full access badge while it may edit;
  • when it last did anything;
  • Unlink, which asks you to click again.

Names are yours, and each computer names the other. Two machines given the same name are numbered: linux, linux (2). Activity lists the 20 latest jobs in both directions, with the machine, the agent, the first line of the prompt, the status and how long each took. A job another computer is running here has a Stop button; the computer that asked is told Stopped on by its user.

Job history on this computer lists every job other computers started here, joined to how it ended, and every one this computer refused, with the reason. Unlike Activity it is kept across restarts (see Records below). A job's row shows the machine, the agent, edit for a job with edit permission, the folder, the first line of the prompt and any credential rules screening matched; a refusal's row shows the machine, the reason and how many more came that minute.

Notify me when a linked computer starts a job here is on by default. Each job another computer starts here then shows a system notification naming the computer, the agent, read-only or edit, and the folder, with the task's first line quoted under them; several from one computer within a minute share one notification. Click it to open this pane. A refused request never notifies, and neither does anything while the switch is off. The notification is not a question: the job is already running, and Stop is how to end it.

What a linked computer may do here

Each computer decides what the other may do on it, per linked machine. You choose Run agents here when the link is made, and you can change either permission at any time:

Permission Default What it allows
Run agents here (read-only) on Start a headless agent here in its own read-only mode. It can read any file you can read. Claude runs with only Read, Grep and Glob, Codex in its read-only sandbox, and Gemini (agy) in plan mode. Each gets this machine's memory and code index as read-only Termpolis tools (see Read-only tools below). Codex and agy keep MCP servers you added to their own configs.
Let agents edit files and run commands here off The agent here runs unattended, as you. Claude and Gemini get full access: permission prompts skipped and a shell, so they can read and change any file you can, use your Termpolis terminals and reach your other linked computers. Codex runs in its workspace-write sandbox: it changes files only in the job's folder and temporary folders, and Termpolis switches the sandbox's network off for the job (-c sandbox_workspace_write.network_access=false), whatever your Codex config says. Codex's sandbox is what enforces both. On a Windows computer, treat a Codex edit job as full access too: there Codex sandboxes with separate user accounts and firewall rules, and that they keep a job from the computer's own local services, Termpolis's MCP server among them, hasn't been confirmed; the pane says so on Windows. Turning it on turns on Run agents here too.

Edit is never chosen along with a code. Whoever enters a code first hasn't had its words checked yet, so a new link can at most run read-only agents. To let a computer edit, confirm its words first, then tick the permission on its row: a panel says what it means for each agent, and only Allow edit turns it on. Switching it off needs no confirmation. A link made with edit by an earlier version keeps it if its words were already confirmed, and loses it when they are confirmed if they weren't.

The risk is stated plainly. A machine with Run agents here can have an agent read any file you can read on this computer and send it back. With edit, a Claude or Gemini agent it starts here can do anything you can. Grant edit only to a machine you trust as much as this one.

Permissions are checked on the computer that does the work, before any agent starts. They are checked again when you change them:

  • switching edit off stops that machine's running edit jobs;
  • switching Run agents here off stops all of its jobs;
  • unlinking stops all of its jobs.

Stopping a job doesn't undo what it already changed.

How your agent decides to use another machine

Termpolis doesn't split your work or schedule anything across machines. Your agent gets one MCP tool, linked_machines, and uses it the way it uses any tool: when you ask it to, naming the machine you gave a name to ("have codex on linux run the integration tests and summarise the failures"), or when it judges a task belongs on the other computer, say because Codex is blocked on this network, or the repo, the hardware or the test environment is over there. A workflow you build can also call it as a step.

A typical exchange:

  1. list: which machines are linked and online, which agents each has installed, and whether this computer may run or edit there.
  2. run: a machine, an agent and a self-contained prompt, optionally a folder on that machine and write: true. The agent over there has none of this conversation, so the prompt carries what it needs: a commit SHA, earlier findings.
  3. The answer comes back as the tool result, or, for a job still going after the wait, as a jobId the agent collects with result.
  4. Your agent reviews the answer like any other and carries on: it can check the commit the other agent pushed, ask a follow-up in a new run, or report back to you.

Asking another machine: the linked_machines tool

Agents get one MCP tool, linked_machines, with three actions:

Action Arguments Returns
list — thisMachine, and for each linked machine name, online, confirmed, agents (installed there), canRun, canWrite, and a note explaining anything it could not find out
run machine, agent (claude, codex or gemini), prompt; optional cwd, write, model, waitSec jobId, machine, agent, status (running, done, failed or cancelled), plus output, truncated, error, durationMs and note where they apply
result jobId; optional waitSec the same as run
  • machine is the name you gave it on this computer, in any case. If no machine has that name, the error lists the names of the linked machines.
  • prompt must be self-contained, up to 20,000 characters, because the agent over there has none of this conversation. It gets the prompt after one line. That line names the computer that asked and the working folder, and says its final message is returned to the agent that asked. Like any headless run there, it also starts with that computer's own memory primer (up to 6,000 characters), so its answer can quote that machine's memory.
  • cwd is a folder on the other machine, absolute or starting with ~. It defaults to the home folder and must exist there. UNC network paths (\\server\share) are refused.
  • write: true works only if that machine lets this one edit.
  • model picks the agent's model there. By default the agent uses its own.

run waits up to 45 seconds by default for the answer (waitSec, at most 50). That stays under the 60 seconds Codex gives a single tool call. A job still going when the wait ends comes back with status: "running", its jobId, and a note saying to call result with that jobId, which waits the same way.

The jobId names the link, not the machine's name. It still works after a rename or after this computer restarts, as long as the other computer still holds the job. A finished job is kept there for up to 2 hours (at most 100), in memory, so restarting that computer forgets it.

Losing touch with the other computer while run waits on a job is not reported as a failure. You get the job's last known status and a note to check again with result. Every failure comes back as data, { "error": "…" }, worded for the agent to act on or pass on to you.

Termpolis never pre-approves this tool. Claude Code asks you before using it, unless the session skips permission prompts (as swarm workers do), and Codex is not told to trust it. Agents and workflow steps together can call it at most 30 times a minute, and every call, a workflow's included, is written to the MCP audit log.

Limits

  • Both computers need Termpolis running, with Linked machines on and a connection to the relay. The relay is a meeting point, not a mailbox, so a request to a machine that isn't there fails at once with "linux" is offline — Termpolis must be running there. instead of waiting.
  • A computer that is asleep or shut down is offline. While it runs a job for another computer, Termpolis keeps it from idle-sleeping, and lets go when the last such job finishes, fails or is cancelled. Only idle sleep is held off: the display can still turn off, and closing a laptop's lid or choosing Sleep can still put it to sleep.
  • On a Mac, closing the window does not take the computer offline. Termpolis keeps running in the Dock, and its links with it, as do Remote and the agents' MCP connection. Quit it (⌘Q, or Quit from the Dock) to go offline.
  • Every run is a fresh headless session. No conversation carries over, and a job doesn't become that computer's memory (see Memory under How it is secured below), so the prompt carries what the other agent needs: a commit SHA, earlier findings. Code moves between the machines through Git as usual. Linked machines does not copy files.
  • A job runs for up to 15 minutes by default. After that it is stopped.
  • A computer runs at most 2 jobs at a time for any one machine, and 4 in all. Past that a request is refused with a message starting busy:.
  • The end of a long answer is kept. That is up to the last 200,000 characters, fewer if the answer is dense with control characters, so it still fits in one relay frame. Agents put their conclusion last. A shortened answer says truncated: true. A failed job's error keeps its end too: past 4,000 characters, its last 3,999 behind an ellipsis (…).
  • Up to 16 linked machines per computer, counting the ones this computer made codes for and the ones whose codes it entered.
  • Jobs live in memory. Quitting Termpolis stops every job it is running for other computers, and forgets the finished ones. The job history keeps a line for each.

How it is secured

  • End-to-end encrypted. It uses the same X25519 pairing, ChaCha20-Poly1305 sealing and untrusted relay as Termpolis Remote. Prompts, answers, folders and machine names are sealed under keys derived with HKDF-SHA256; each direction has its own key, and a counter in every frame rejects replays.
  • Fresh keys on every connection. Each connection mixes in new ephemeral keys, so prompts and answers recorded today can't be decrypted later, even if a computer's long-term key leaks.
  • An encrypted relay. Making or entering a code requires a wss:// relay (ws:// only to localhost), and Settings → Remote accepts nothing else. Links this computer created reconnect through that setting; if it still holds a plain ws:// address saved by an older version, Termpolis connects none of them through it and both panes say so until it is changed. Links made by entering another computer's code use that computer's relay. The relay's source is in relay/, so you can run your own.
  • What the relay does see: both computers' IP addresses, a room id for the link, when each computer connects, the size and timing of each frame, and each frame's unencrypted header (public keys while pairing and connecting, a frame counter). It stores no frames; the hosting provider's logs keep connection metadata.
  • Keys. The computer that enters a code makes a fresh key pair for that link alone, as a phone does for each desktop. The computer that made the code uses its one identity key, the one its phones pair with, for all its links. A separate key per link there would protect nothing more: every key sits in the same data folder under the same OS account, so whatever can read one can read them all. Private keys are encrypted with the OS keychain (DPAPI, Keychain or libsecret) where one is available; on Linux without a keyring they are stored unencrypted in the data directory, and this pane says so. A key the keychain can't open is never replaced: no link can be made or changed until it opens again, or until you choose Start over, which costs every link and every phone (see Remote's keys). When only the file of links can't be opened and the identity still can, this pane says so, and its Start over sets that file alone aside: the links this computer joined have to be made again, and a computer that entered a code from this one has to be confirmed again, but every phone keeps working. The file is written in one step, so a crash mid-save can't leave half of it.
  • Confirmed on each computer. A computer runs nothing for another until you confirm the eight words on it. Before that, the other computer can learn only this one's name and Termpolis version. Treat the code like a one-time password: whoever uses it first can ask to link, so cancel if the other screen shows no words or different ones.
  • Read-only by default. Every request is checked on the computer that would do the work, before any agent starts. The computer doing the work doesn't ask you per job, but it tells you: each job shows a notification and is kept in the job history, and a running one can be stopped from Activity.
  • Jobs, nothing else. A linked computer is never given a phone's abilities. It cannot list, read or type into your terminals; it can only ask for the jobs described here. A Claude or Gemini job with edit permission runs as you, though, and can do whatever you can.
  • Read-only tools, and no passing the work on (v1.51).
    • A delegated job keeps Termpolis's MCP server, cut down to this machine's memory and code index: memory_search, memory_list, memory_related, memory_graph, memory_anticipate, memory_selfcheck, memory_conflicts, the six code_* tools, get_git_status, test_coverage and retrieve_full. No memory writes, terminals, swarm, gateway or other machines.
    • The job is marked in its environment (TERMPOLIS_LINKED_JOB), and Termpolis's agent connection (the stdio adapter) enforces the list: it offers the job only those tools, and refuses anything else as a tool result. linked_machines is refused with Nested delegation is not allowed: this agent was itself started by a linked machine.
    • Claude gets a per-run config holding only Termpolis's server with the marker (--mcp-config plus --strict-mcp-config, so none of your other servers load), those tools pre-approved, and in a read-only job only Read, Grep and Glob besides. Codex gets the marker on its termpolis server for that one run, and the tools pre-approved, when its config.toml holds Termpolis's own server; otherwise the server is switched off for the run. agy passes its environment, marker included, to the server itself.
    • These limits bind read-only jobs. A Claude or Gemini job with edit permission has a shell and runs as you, so it can get around them, for example by calling Termpolis's local MCP server with its token: treat edit as full access. A Codex edit job's commands run in Codex's sandbox with its network switched off, and that sandbox is what keeps them from that server. On Windows that hasn't been confirmed (Codex's sandbox there is separate user accounts and firewall rules, which may not cover the computer's own local services), so treat a Codex edit job on Windows as full access too.
  • Answers are labelled and scanned. What comes back from another machine passes the gateway's prompt-injection scan before your agent sees it. That covers the answer, its error and any refusal worded over there. Every answer, and a failed job's error (its stderr, as much the agent's own text as its output), arrives under a label saying it is data from another computer, not instructions from the user; a flagged one gets the UNTRUSTED CONTENT banner instead, telling the agent not to follow it.
  • Vendor tokens and private keys don't cross. Prompts and answers are screened with the AI Security secret rules on both computers: the prompt before it leaves and when it arrives, the answer and its error before they leave and when they arrive (in case the other computer runs a version that doesn't screen). The screen has two tiers:
    • A credential whose own shape names it is removed and replaced with [REDACTED:<rule>]: vendor tokens such as sk-ant-…, ghp_…, github_pat_…, AKIA…, xox…, and whole private-key blocks, including an indented one (a YAML secret) and one whose lines are \n escapes (a JSON service-account file). A key block counts once it holds key material: a long body line or its END line. A note tells your agent how many this computer removed, and by which rules, from the prompt it sent and from an answer that arrived with one still in it; one the other computer removed shows only as its marker.
    • Anything that only looks like a credential is left as it is, because a delegated job's answer is usually code, and its test fixtures and examples look like credentials too (password = "hunter2", a postgres://user:pass@ URL, a JWT, a value assigned to API_KEY, a shape with no vendor's name in it such as a Discord or Telegram bot token, or a private-key header with no key under it, as a PEM parser writes). The answer gets a note naming the rules that matched: possible credential, not removed. Both tiers write a line to the AI Security audit log on the computer that screened: the rule ids and how often each matched, never the value. A credential you deliberately want on the other computer should live in a file there, not in the prompt. An answer and its error are screened before they are cut to their ends, with up to 65,536 characters before the cut in view. That reaches back past the BEGIN line of any key block a rule takes (at most 400 lines under it, which a key file writes 64 to 76 characters wide) and the start of any token, so a key or token the cut runs through is still found and removed whole, and the end that is kept starts with its whole marker. A credential wholly before the end is not counted. Where the end is cut again from its start (markers made it longer than it may be, or it must fit one relay frame), the characters cut are ones outside markers: every marker in its way stays, whole, so each credential counted still shows. A marker can be longer than the credential it replaced; where that makes the end too long, the characters cut are the ones before the marker, and where there are fewer of those than the marker added, the first ones after it (a cut 2 characters before an AWS key, which is 20 characters with a marker of 25, takes those 2 and the next 3). Only where the markers alone are longer than the end may be do the first of them go, whole; they are still counted. Text in the answer that already reads like a marker ([REDACTED:…] in a log printed again, say) is kept the same way, so when the end is cut to fit a relay frame, such text from the part cut off can move to its front. This holds where the computer doing the job runs a version after 1.53: 1.53 cut first and screened what was left, and 1.50 to 1.52 sent it unscreened, so a key or token their cut ran through arrives without its start (a BEGIN line, a ghp_ prefix), which no rule recognises, and the rest of it crosses.
  • Memory. The agent doing the work starts with that computer's own memory primer (up to 6,000 characters), as a local run would, so its answer can quote that machine's memory. Nothing of the job becomes that computer's own memory: Termpolis doesn't write the answer there, Claude Code and Codex are told not to save the session (--no-session-persistence, codex exec --ephemeral) when the installed version can be, and the memory indexer and the learning reflex skip a session another computer started even if it was saved, and the indexer skips the transcripts of the subagents it ran too. What versions 1.50 to 1.52 had already stored from such jobs is demoted once, whole sessions at a time, the first time the indexer finishes a full pass after the update. A demoted chunk is still stored and still found, ranked lower. Gemini jobs run through agy, whose own conversation store Termpolis doesn't read.
  • Your AI providers. Each agent's provider sees what that agent is asked and answers, as always, and a job uses the doing computer's AI account.
  • Records. The asking computer's MCP audit log records each call an agent makes over MCP (not the prompt). The doing computer keeps its Activity list in memory until Termpolis quits, and its job history in linked-jobs.jsonl in the data directory: a line when a job starts (the machine, agent, read-only or edit, folder, the first line of the screened prompt and the credential rules matched), a line when it ends (how, and how long it took), and a line for each refusal with its reason. The prompt's first line is kept, and shown in the job's notification, with anything that might be a credential taken out, even what the agent was given because it only looked like one. A computer's refusals within one minute after the first are written as one line with their count, so a computer refused again and again adds at most two lines a minute, not one for every request. Never the prompt or the answer. Past 2 MiB the file moves to linked-jobs.prev.jsonl, replacing the one there. It is a record, not evidence: a Claude or Gemini job with edit permission runs as you and could rewrite it like any other file you own.

Unlinking

Click Unlink twice on either computer. The other computer drops the link too and says so ("laptop" unlinked this computer.), and any job either one still had running on the other is stopped. If the other computer is offline at the time, it keeps showing this one as offline until you unlink it there as well.

The wire format is in docs/remote-wire-format.md §13.


31. Architecture

┌─────────────────────────────────────────────────────┐
│  Renderer (React)                                   │
│  ├── Sidebar, Terminals, Panels                     │
│  ├── Activity Feed (observability UI)               │
│  ├── Swarm Dashboard + Conductor view               │
│  └── IPC client → window.termpolis bridge           │
└──────────────────┬──────────────────────────────────┘
                   │  Electron IPC
┌──────────────────▼──────────────────────────────────┐
│  Main process (Node)                                │
│  ├── Terminal manager (node-pty)                    │
│  ├── Session persistence (session.json)             │
│  ├── Git adapter                                    │
│  ├── MCP server (HTTP, 127.0.0.1 only)              │
│  ├── Swarm memory (JSONL + embeddings)              │
│  ├── AI conductor (spawns Claude Code as a child)   │
│  └── Watchers (event bus + alerts)                  │
└──────────────────┬──────────────────────────────────┘
                   │  localhost:9315 (MCP)
┌──────────────────▼──────────────────────────────────┐
│  AI agents (Claude, Codex, Gemini)                  │
│  Each in its own pty-backed terminal                │
└─────────────────────────────────────────────────────┘

Tech stack:

  • Electron 29, React 18, TypeScript 5, Vite 5 (electron-vite)
  • node-pty for terminals, xterm.js for rendering
  • Vitest for unit tests (2100+ tests, >90% line coverage)
  • Playwright for E2E + screenshot captures
  • electron-builder for packaging, Azure Trusted Signing for Windows, notarytool for macOS

32. Keyboard Shortcut Reference

Everything listed in Settings → Keybindings can be rebound there, except the three copy shortcuts. The panel shortcuts (the command palette, the Swarm dashboard and so on) are fixed. Defaults:

Action Windows / Linux macOS
New terminal Ctrl+Shift+T ⌘⇧T
Close terminal Ctrl+Shift+W ⌘⇧W
Next terminal Ctrl+Tab ⌃Tab
Previous terminal Ctrl+Shift+Tab ⌃⇧Tab
Jump to terminal 1–9 Alt+1…9 —
Launch Claude Code / Codex / Gemini CLI Ctrl+1 / Ctrl+2 / Ctrl+3 ⌘1 / ⌘2 / ⌘3
Toggle split view Ctrl+Shift+G ⌘⇧G
Toggle sidebar Ctrl+B ⌘B
Find in terminal Ctrl+Shift+F ⌘⇧F
History search Ctrl+Shift+H ⌘⇧H
Clear terminal and scrollback Ctrl+Shift+X ⌘⇧X
Keyboard select mode Ctrl+Shift+Space ⌘⇧Space
Anchor select (click start, click end) Alt+Shift+Click ⌥⇧Click
Copy (when text is selected) Ctrl+C ⌘C
Copy as plain text Ctrl+Shift+C ⌘⇧C
Copy for Teams/Slack Ctrl+Shift+K ⌘⇧K
Copy as code block Ctrl+Shift+Q ⌃⇧Q
Paste Ctrl+V or Ctrl+Shift+V ⌘V
Voice dictation (when enabled) Ctrl+Shift+L ⌘⇧L
Command palette Ctrl+K ⌘K
Settings → Keybindings Ctrl+/ ⌘/
Prompt templates Ctrl+Shift+P ⌘⇧P
Context panel Ctrl+Shift+E ⌘⇧E
Pinned context panel Ctrl+Shift+B ⌘⇧B
Changes panel Ctrl+Shift+J ⌘⇧J
Account menu (with an account added) Ctrl+Shift+N ⌘⇧N
Conversation search Ctrl+Shift+I ⌘⇧I
Activity feed Ctrl+Shift+A ⌘⇧A
Redundancy panel Ctrl+Shift+D ⌘⇧D
Efficiency panel Ctrl+Shift+Y ⌘⇧Y
Memory panel Ctrl+Shift+M ⌘⇧M
Swarm dashboard Ctrl+Shift+S ⌘⇧S
App log Ctrl+Shift+O ⌘⇧O
New terminal, system-wide Win+Shift+T ⌃⌥T
Swarm dashboard, system-wide Win+Shift+S ⌃⌥S
Settings Sidebar gear icon Sidebar gear icon
Git panel Sidebar → Git Panel Sidebar → Git Panel
Workflow Orchestrator Sidebar → Workflows Sidebar → Workflows

Ctrl+C copies only when text is selected; with nothing selected it reaches the shell as the usual interrupt. On macOS, ⌃C always interrupts and ⌘C copies.

The system-wide shortcuts work even while Termpolis is minimized, but only when no other program already owns the combo. If one does, Termpolis skips it and notes that in the app log. On Windows, Win+Shift+S is normally taken by the Snipping Tool.

On macOS, ⌘⇧Q is the system Log Out shortcut, so use ⌃⇧Q for Copy as Code Block.


Final note

Termpolis is under active development. If you hit a rough edge, open an issue at github.com/codedev-david/termpolis. If it's useful to you, consider sponsoring the project.

— David