Termpolis

Documentation

Termpolis Docs

The definitive guide to Termpolis — Secure AI-Assisted Development. The local-first multi-agent terminal where Claude, Codex, and Gemini work as a team without your source code leaving the machine. Built-in AI Security Center, swarm orchestration, MCP server, and a one-shortcut path from terminal to Slack, Teams, or a PR. Every feature, every panel, every shortcut.

🐛 Submit a bug report ✨ Request a feature

Overview

Termpolis welcome screen with AI agents and swarm.
The welcome screen, where new sessions begin.

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:

🛡 AI Security Center

Termpolis ships an in-app AI Security Center at Settings → AI Security. Its goal is to give administrators visibility and layered controls around outbound AI traffic so a team can adopt Claude Code, Codex, and Gemini CLI with the obvious accidents caught and a verifiable record of what was sent — knowing that no in-the-loop tool can guarantee a hosted-model prompt never reaches the provider.

What it is and isn't. Any tool that lets you talk to a hosted model is, by definition, sending your prompt to that provider. Termpolis cannot air-gap a prompt you choose to send and cannot guarantee a provider's stated retention policy is enforced server-side. The Security Center is defense in depth — it catches recognisable secrets, flags oversize code/.env pastes, detects unexpected network endpoints, and writes everything to a local audit log. For absolute air-gap, run a local model and accept the quality / hardware trade-off.

Design principles:

  • Local-first. Every check runs on the machine. None of these features send data to Termpolis or any third party.
  • Native, not a browser/IDE plugin. Termpolis is a terminal manager, not a Chrome extension piping your buffer to a SaaS backend.
  • No telemetry. No login, no phone-home. Optional crash reporting is opt-in and redacts user-folder paths first.
  • Verifiable. Every claim links to the provider's published ToS page; a weekly drift watcher (v1.11.52) opens a tracking issue when those pages change.
  • Honest about limits. Each control below names what it can not catch.

Per-provider training-disposition facts

The panel summarizes how each provider treats prompts on their commercial tier, sourced from the official ToS pages and updated with each release of Termpolis:

  • Anthropic (Claude Code). API + commercial usage — default off for training.
  • OpenAI (Codex). API platform — default off for training.
  • Google (Gemini CLI). Paid tier (API key, Vertex AI, Code Assist) — excluded from training. Free OAuth tier — Google may use prompts to improve products. Flagged yellow.

Watch every prompt (v1.25.2)

Once you launch claude, codex, or gemini in a terminal, every Enter and every paste-sized chunk (≥32 bytes) is scanned in main-process memory against 97 secret rules. The scan runs on a shadow copy. Every byte you type is forwarded to the agent immediately and unmodified — nothing is withheld, nothing is rewritten — and the scan never runs per keystroke, only on submit or paste (~0.05 ms per prompt). Non-AI terminals are not scanned at all. It cannot be turned off. Coverage spans:

  • AWS (access keys AKIA…/ASIA…, secrets, session tokens).
  • GitHub (PAT ghp_…, fine-grained github_pat_…, OAuth secrets, runner tokens), GitLab, Bitbucket.
  • Azure (Storage AccountKey, SAS signature, connection strings, AD client secret, DevOps PAT) and GCP (service-account JSON, OAuth client ID).
  • 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).
  • Project mgmt (Linear, Notion, Asana, Jira, Figma), package registries (npm, PyPI, Docker Hub).
  • Secrets vaults (HashiCorp Vault, Doppler, 1Password Connect), DB connection strings (Postgres/MySQL/MongoDB/Redis), HTTP basic-auth URLs.
  • JWT, PEM/GPG private key blocks, and the .env-style catch-all (SECRET_KEY=…).
  • Named secrets in config-shaped text.env, appsettings.json, YAML, connection strings, and URLs carrying credentials. The audit entry reports the name (DB_PASSWORD), never the value.
  • Shapeless secrets you introduce in your own words"here is the api key for this code, add it to line 42: 8f3a9b2c4d5e…". No token-shape rule could ever see that one. It stays quiet on ordinary talk like "please rotate the api key in production".

Known token shapes (AWS, OpenAI, GitHub, Google, Stripe, JWT) are caught bare, in prose, with no name needed. A manual paste-and-scan box also lives in Settings → AI Security for one-off checks of clipboard text before pasting elsewhere.

A hit is recorded, not blocked. It is written to the audit log as a prompt_secret_sent event that names what leaked — DB_PASSWORD (env_secret) — and never the value. The secret itself is never captured and never written to disk, not even a fragment. The name is the part you can act on: it is what tells you which credential to rotate.

Why this is detection and not prevention. Through v1.25.1 the app claimed to redact a secret before it reached the PTY. That has been removed, because it could never have worked. To redact before the PTY you must withhold the keystrokes — which broke typing outright — and even a working version would buy you nothing: against a TUI agent like Claude Code, your text is already sitting in the agent's own line buffer by the time you press Enter, so writing a "clean" copy to the PTY would only append to it. You cannot un-send what the agent already holds. So the prompt path is detection only, by design, and saying so plainly is better than the old claim, which was false. Prevention lives at the two boundaries where it is actually possible: the git boundary, where a commit or push carrying a key is genuinely blocked, and the memory layer, where a secret is stripped before it can ever be written to the brain.

Dropping redaction is also what made the rule set bigger. A false positive used to mangle your prompt; now it costs one line in a log. That is what made the 7 name-aware and narrative rules above safe enough to ship.

Be clear-eyed about the limit. This does not stop a secret reaching a model through a prompt, and it is not a comprehensive DLP solution. A bespoke corporate token that nobody publishes a shape for, pasted with no name attached, can still go unnoticed and must be vetted separately.

Commit & push Secret Shield (v1.25)

The watch above only ever sees text on its way to an agent — and there, all it can do is record. It never saw git. So a key that an agent wrote into a file — or one you pasted into a config and forgot about — could still be committed, land in your history, and be pushed to a remote without a single check firing. That was the hole. Git is a boundary Termpolis can actually hold, so this is where prevention lives: the Secret Shield runs the same secret-rule engine at the git boundary:

  • On commit — it scans the staged diff (git diff --cached), which is precisely what the commit is about to capture.
  • On push — it scans every unpushed commit patch, which is precisely what the push is about to send. So a secret already sitting in local history is caught before it leaves the machine, not just one you're adding right now.

A hit blocks the git operation and tells you which rule fired, so you can strip the value (or rewrite the offending commit) and go again. It is a hard stop, not a log line: unlike the prompt path — which can only tell you what has already gone out — the commit simply does not happen. Nothing is silently rewritten behind your back, and no half-made commit is left behind.

Covering git you type yourself: the hooks (v1.25.1)

Out of the box the shield gates the git operations Termpolis itself runs: the Git panel and Swarm Review. That leaves the way most people actually commit — git commit typed into a terminal — going straight past it. So install the hooks: Settings → AI Security → Protect a repository writes a pre-commit and a pre-push hook into that repo, and the secret is caught however you commit — terminal, IDE, or script.

  • It works with Termpolis closed. The hook shells out to a standalone scanner that carries its own copy of the rule table and needs only Node and git. A hook that only guarded you while the app was running would silently stop guarding you the moment you quit — worse than no hook, because you would still believe you had one.
  • It chains, it does not clobber. An existing POSIX-shell hook (husky, lint-staged) is preserved and still runs, and its exit code still gates the commit. The shield is spliced in as a sentinel-delimited block below the shebang, runs first, and then falls through to whatever was already there — so uninstalling gives you back the original file byte for byte.
  • A non-shell hook is skipped, and that repo stays unprotected. If the repo already has a hook written in something other than POSIX shell — the pre-commit framework generates Python ones — Termpolis refuses to touch it rather than corrupt it by injecting sh into a Python file. That is the right call, but be clear about what it means: no shield is installed there. The Protect-a-repository panel is where you find out which repos actually took the hook.
  • It fails open. If Node is missing, or Termpolis has been uninstalled and the scanner is gone, the hook exits 0. A hook left behind by a deleted app must never wedge your git.

Fixed in v1.25.6 — the panel could report PROTECTED when it was not. The list of protected repositories was compared with a bare string !==. But an install stores either the path the renderer supplied (forward slashes) or the one the native folder picker returned (OS-native, backslashes on Windows) — so installing a repo via the picker and then uninstalling it from the working directory never matched. The repo stayed in commit-shield-repos.json and the panel kept listing it as protected after its hooks had been removed. The entry is now keyed on a canonical path (resolved, separators unified and case-folded on Windows only — on macOS and Linux a backslash is a legal filename character and the filesystem is case-sensitive, so folding either would conflate two genuinely different repositories). That also stops one repo being stored twice under two spellings. A security control that claims to be armed when it is not is worse than one that admits it is off.

Be clear-eyed about the limit. git commit --no-verify bypasses any git hook — that is git’s design, and it is your machine. This is a strong net, not a cage. It is here to stop the accident, not a determined author.

It fails open. If git itself errors — not a repository, no upstream, git off the PATH — the shield gets out of the way and lets the operation proceed. It can block you for a secret and nothing else; it will never wedge a commit for a reason that has nothing to do with secrets.

On by default — toggle it under Settings → AI Security → Commit Shield. It inherits the rule engine's limits: these are high-confidence patterns, so a bespoke corporate token shape may not be recognised and still needs its own review.

Gemini account-mode auto-detect + Strict Mode

The Gemini CLI is the highest-risk surface, because the free OAuth tier may be used for product improvement. Termpolis inspects the running shell environment to identify which tier the CLI will hit:

  • Vertex AIGOOGLE_APPLICATION_CREDENTIALS + GOOGLE_CLOUD_PROJECT.
  • Code Assist (Workspace)GOOGLE_GENAI_USE_GCA=true.
  • Paid API keyGEMINI_API_KEY or GOOGLE_API_KEY.
  • Free OAuth fallback — none of the above. Flagged in red.

When Strict Mode is enabled, Termpolis intercepts gemini invocations from any terminal. If the resolved account mode isn't paid-tier-safe, the launch is cancelled with Ctrl+C, an in-band ANSI banner explains why, and the gemini text never reaches the PTY. The blocked launch is recorded in the audit log as BLOCKED: strict-mode + free-tier. So unlike the prompt watch — which never withholds anything — Strict Mode genuinely does stop the command from running.

Strict Mode is the one security control that ships OFF. The audit log, the Commit Shield, the Egress Guard and the memory scrub all default to on, and the prompt watch cannot be turned off at all. Strict Mode is opt-in because it is the only one that can refuse an action you meant to take — and a control that blocks legitimate work without being asked is a control people rip out.

Be clear-eyed about the limits. Detection is an env-var heuristic, so a Workspace Code Assist licence that carries no environment variables is misclassified as free-tier and will be blocked — the panel says so in place. Strict Mode also only intercepts shell-level invocations of the gemini binary: it does not cover out-of-band paths (a renamed binary, a script that calls the Google API directly, a Gemini session started outside Termpolis). It is a tripwire for the common-case mistake, not a proxy.

Agent command enforcement (swarm)

In a swarm, the AI conductor is itself a model — and it is the thing deciding what commands the other agents run. That is a genuine injection surface: an agent whose output the conductor reads can try to talk the conductor into launching the next agent with different flags. So the conductor is not trusted to compose a launch command.

src/main/agentCommandSanitizer.ts intercepts every swarm-launched agent command and rebuilds it from the approved base command for that agent. Anything the conductor tried to add is dropped:

  • Unauthorized flags are stripped — notably -p (headless print mode) and --sandbox, which change what the agent is allowed to do.
  • An appended prompt is stripped. The conductor delegates work through the swarm's message channel, not by smuggling instructions onto a command line.
  • Only exact model aliases survive. --model is checked against an authoritative allowlist (opus, sonnet, haiku, fable — Claude only). The value is whitespace-tokenised and matched exactly, then the command is rebuilt from the trusted base plus that alias — so a quoted, concatenated, or injected value can never ride along.

Limit: this governs the MCP command surface — the launch commands Termpolis runs on the swarm's behalf. It does not constrain arbitrary subprocesses an agent spawns for itself once it is running; that is what the Egress Guard and the sensitive-file watcher are for.

Local JSONL audit log (on by default since v1.25)

Every AI-agent terminal launch is recorded in ai-security-audit.jsonl inside the Termpolis data directory (%APPDATA%\termpolis\ on Windows). Each record is one JSON object containing the timestamp, the agent, the terminal id, and (optionally) byte counts and hit counts. The file is append-only with 10 MB rotation. It can be wiped from Settings → AI Security at any time.

It now records by default. Through v1.24 the audit trail defaulted to off — which meant that for most users the file did not exist at all, and the evidence you would most want after an incident was never being written in the first place. As of v1.25 it records out of the box.

The event vocabulary — what you will actually find in the file:

  • prompt_secret_sent — the prompt watch matched a credential in something you sent to an agent. Records the name and rule (DB_PASSWORD (env_secret)), never the value. This is the one that drives the “Rotate these” panel.
  • code_chunk_sent / env_dump_sent — a large code paste or an .env-shaped dump went out. Separate events since v1.25.2, so a big paste no longer masquerades as a leaked key.
  • commit_blocked / push_blocked — the Commit Shield stopped a git operation. Unlike the prompt events, these record something that did not happen.
  • egress_violation — an agent connected to a host outside the provider allowlist.
  • sensitive_file_read — an agent read a high-risk file on its own initiative.
  • import_scan / import_blocked — a Safe Import verdict and install decision.
  • memory_scrub — a secret was stripped before a memory was written. Rule ids only.
  • terminal_open — an AI terminal was launched (and, for a Strict Mode refusal, that the launch was blocked).
The scan is always on; the recording is not. The prompt watch cannot be turned off — but the audit log can. Switch it off and the watch keeps scanning while nothing is written down, which means you lose the one thing the prompt path can actually give you: the list of credentials to rotate. The panel says so in place rather than pretending to be armed.

Read it without leaving the app (v1.25.2). Settings → AI Security → "Open the audit log" opens a viewer over the raw JSONL, led by a "Rotate these — they were sent to a model" panel. That panel lists every prompt_secret_sent event the prompt watch recorded, by name (DB_PASSWORD (env_secret)). The value is not shown because it was never captured — the name is the part you act on, and rotating it is the remedy the prompt path can actually give you.

Fixed in v1.25.6 — a NaN limit dumped the entire log. Reads of the log are capped (a default page of 200, a hard ceiling of 2,000) so a renderer-side call can never haul the whole file into memory at once. The guard checked typeof limit === 'number' — which is true for NaN. So NaN took the clamp arm instead of the default, and Math.min/Math.max propagate NaN rather than clamping it; the resulting slice(NaN) degrades to slice(0) — “return everything”, which is the one thing the cap exists to prevent, and it was reachable straight from the renderer. The guard is now Number.isFinite.

The audit log captures only what Termpolis observes locally — activity that bypasses Termpolis (a Gemini CLI run from a separate native terminal window, for example) is not visible to the audit log.

Sensitive-file-read alert (v1.11.53)

The prompt watch can only see what you type. When the AI agent autonomously decides to read a sensitive file via its own Read tool (or runs cat/head/grep on one through Bash), the file's bytes have already been added to the agent's context and transmitted to the provider on the next turn. The terminal-side scanner never sees that path. This watcher closes the gap.

How it works. src/main/sensitiveFileWatcher.ts subscribes to agentEventBus tool_call events emitted by the transcript watchers (Claude Code / Codex / Gemini). For each event it inspects the tool name and arguments:

  • Filesystem-style tools (Read, Edit, Write, read_file, view, …) → pulls file_path / path / filename from the input.
  • Shell-style tools (Bash, run_shell_command, container.exec, …) → tokenises the command, splits on &&/;/|, and identifies positional arguments to cat/head/tail/grep/cp/curl -F file=@ and 23 other reader commands (29 in all, including the PowerShell Get-Content/Select-String family).

Each candidate path is matched against a hand-curated rule list: .env/.env.local/.env.production (excluding .env.example/.sample/.template); *.pem/*.key/*.p12/*.pfx/*.jks/*.keystore; id_rsa/id_ed25519/id_ecdsa/id_dsa (excluding .pub); ~/.aws/credentials + ~/.aws/config; GCP service-account JSONs; files under ~/.ssh/ excluding known_hosts/config/*.pub; .netrc/.npmrc/.pypirc; ~/.docker/config.json; ~/.kube/config + *.kubeconfig; Azure credentials; database-URL config; secrets.{yml,yaml,json,env,toml,ini}; credentials.{yml,json,…}; the GnuPG private key store (secring.gpg and *.key under .gnupg/, excluding the public pubring.gpg/pubring.kbx); *.kdbx/*.kdb (KeePass); and Chrome/Firefox/Edge cookie databases.

Fixed in v1.25.6 — the GnuPG rule could never fire. secring.gpg, your private keyring and the single file that rule exists to catch, had been grouped into the rule’s own exclusion list alongside the public keyrings. The exclusion returned false before the match could ever return true, so an agent reading your private GnuPG keyring was never flagged — and the failure mode was total silence, which reads exactly like “nothing happened”: the worst possible failure mode for a watcher. Only the pubring.* entries are excluded now, and a test pins it.

On a match the watcher writes an audit entry tagged sensitive_file_read (with rule id, tool name, source path) and fires a terminal:sensitive-file-read IPC event to the renderer which displays a banner naming the file and the agent. A per-terminal counter is exposed via aiSecurity.sensitiveReads(terminalId) so the Security panel can show "3 sensitive reads this session" alongside the running list.

What it cannot catch. The bytes have already been transmitted by the time the watcher runs — the transcript JSONL is downstream of the agent's tool runtime. Files exfiltrated via subprocesses the agent invokes through arbitrary code (e.g. python -c 'open(".env").read()', network sockets opened by langchain wrappers, MCP servers run by the agent itself) are only caught if the wrapping Bash command parses cleanly to one of the known reader commands. The watcher is intentionally conservative on the false-positive side — flagging an irrelevant file once is cheap; missing a leak is expensive.

Code-chunk + env-dump heuristics (v1.11.52)

Outbound prompts larger than 2 KB are inspected for code-shaped structure (indentation density, braces/punctuation density, common keywords like function/class/import/def, and module declarations such as import … or #include). When two or more of those signals fire, the prompt is flagged to the UI (terminal:code-chunk-detected) and written to the audit log as a code_chunk_sent event. A separate detector counts UPPER_SNAKE=value lines (with an optional export prefix) — five or more raises env_dump_sent, which records the variable names and how many there were, never the values.

These got their own event names in v1.25.2, and that matters more than it sounds. They used to be logged as redaction_hit — the same event a real leaked credential produced. That conflated “you pasted a big file” with “you leaked a key,” and inflated the secrets-sent count with things that are not secrets at all. Now the “Rotate these” panel only ever lists actual credentials, and a large paste is recorded as what it is.

Both detectors are heuristic, and neither blocks the prompt — they exist so a casual paste of an entire source file or .env does not slip past unnoticed. False negatives are possible on minified or unusual code shapes.

Per-agent egress audit (v1.11.52)

Termpolis asks the OS what TCP connections an AI agent's PID has open (netstat -ano on Windows, ss -tnp on Linux, lsof -nP -iTCP -p <pid> on macOS) and records each unique remote host:port to the audit log, so you can answer "did Claude talk to anything other than api.anthropic.com today?".

The poll is on-demand — it runs when you open the Security panel, not on a background timer. That is a deliberate reversal. The original design polled every 60 seconds per AI terminal, and that triad — enumerate processes, spawn a subprocess, from a freshly-signed executable — was load-bearing in the Windows Defender cloud-ML false positive that quarantined v1.11.55. A continuous behavioural signature is exactly what heuristic AV hunts for. The cost of moving to on-demand is one extra shell-out the first time you open the panel; the benefit is that the security feature stops getting the app flagged as malware.

So this is sampling, not packet capture. A connection that opens and closes between two polls is never observed at all. We do not reverse-DNS the peer and we do not inspect the payload. If the platform tool is missing or you lack the permissions, the poller silently returns nothing and the rest of the app carries on.

Egress Guard — the allowlist policy (v1.25)

The egress audit above records. The Egress Guard turns that record into a policy. Every remote host an agent connects to is checked against the known AI-provider allowlist; anything outside it is raised as a violation and written to the audit trail. Claude talking to api.anthropic.com is expected, and stays quiet. Claude talking to a paste site, an unfamiliar VPS, or a host you have never seen before is something you now find out about instead of having to go looking for it.

Matching is dot-anchored, which is the entire game for an allowlist: a host matches an allowed domain only if it is that domain or a subdomain of it. api.anthropic.com passes. evil-anthropic.com does not pass as Anthropic. A naive substring check would have waved that straight through — and a lookalike domain is exactly the trick an exfiltration endpoint would use.

It flags and audits. It never kills your agent. A violation does not terminate the process or sever the connection — Termpolis is not a firewall and will not pretend to be one in the middle of your task. A false positive must never take down your agent mid-task, so the guard's only output is an egress_violation entry in the audit log.

It also inherits the limits of the poll it sits on: this is sampling, not packet capture, so a connection that opens and closes between two polls is never judged at all. And when DNS resolution fails — you are offline, or the resolver is down — the guard stays silent rather than flagging every provider IP it can no longer name. A guard that cries wolf is a guard nobody reads. On by default.

Memory-at-rest secret scrub (v1.25)

The shared memory ingests your past AI conversations and your repo's source code. Both are places a secret can be hiding — a key you pasted into a transcript three weeks ago, a token sitting in a source file. And a secret that gets embedded into the brain is worse than a secret at rest: it is recallable. A future agent could pull it straight back into its own context on a semantic hit, and from there to a provider.

So the scrub runs before the memory exists. Secrets are stripped out of the text before it is hashed, before it is embedded, and before it is written to disk — not cleaned up afterwards. A key in a transcript or an indexed source file therefore never lands in the store, never gets a vector, and can never be recalled back into an agent's context. On by default.

This complements, rather than replaces, the code indexer's existing denylist, which keeps whole sensitive files (.env, keys, cloud credentials) out of the index in the first place. The scrub is the second line — for the secret that turns up somewhere the denylist was never going to look.

Be clear-eyed about the limits. The scrub runs at write time: it cleans every memory written from v1.25 onward, but it does not go back and re-scrub a store you built before it existed. If you have been running Termpolis for months, assume the older memories were never filtered. It also fails open — if the scrubber itself errors, the memory is still written (an anomaly is recorded) rather than the write being lost, because silently dropping a user's memory is its own kind of data loss. And it inherits the rule engine's regex-shaped coverage: a bespoke token nobody publishes a shape for can still be embedded.

Weekly ToS drift watcher (v1.11.52)

A scheduled GitHub Action (.github/workflows/tos-drift.yml) runs every Monday at 13:00 UTC. It fetches the three provider pages this app cites, normalises the HTML aggressively (strips <script>/<style>/<svg>/<head> and HTML comments, decodes entities, collapses whitespace), hashes the result, and compares it to a snapshot committed under docs/security-snapshots/. When a hash differs the action opens a tracking issue tagged security,tos-drift so a human reviewer can decide whether the legal language has materially changed and Termpolis needs an update. The watcher detects rendered-text changes — it cannot infer legal intent.

This one is maintainer-facing — be clear about what it is not. The drift watcher is CI for this repository: it opens a GitHub issue so the training-disposition facts on the Security panel and on the marketing site stay aligned with what the providers actually publish. It is not an in-app notifier — Termpolis does not pop a banner telling you that OpenAI changed its terms last Tuesday, and it is no substitute for your own reading of the terms you are bound by. What it buys you is that the claims Termpolis makes about your providers do not quietly rot.

Legal disclaimer

The AI Security Center is best-effort, not regulatory-grade. The project is licensed Apache 2.0 "AS IS". The full disclaimer ships in TERMS.md §5a and inside the app at Settings → AI Security. Provider terms can change without notice; you must verify provider terms before transmitting confidential data. To the maximum extent permitted by law, the authors and contributors of Termpolis disclaim all liability for any data leak, breach, regulatory violation, contractual breach, or business loss arising from your use of any AI agent launched through this application.

🔍 Safe Import — vet a skill, plugin, or MCP server before it runs (v1.25)

The agent ecosystem now trades in shared configuration: skills, plugins, slash commands, subagents, and MCP servers, passed around as a .zip or a folder from a repo. Every one of them is code and instructions that your agent will run with your permissions — and the normal way to install one is to unpack it into ~/.claude/skills and hope. A malicious skill doesn't need an exploit. It just needs you not to read it.

Safe Import (Settings → General) is the front door. Point it at a .zip or a folder. Termpolis stages the artifact in a quarantine directory, statically analyses every file inside it, and shows you a verdict before anything is installed anywhere your agents can reach.

It is a static scanner, an install gate, and runtime egress monitoring — it is not a sandbox. Termpolis never executes the artifact to find out what it does; nothing is detonated in a jail. “Quarantine” here means staged and read, not installed — the files are analysed as text, then refused or admitted on the evidence. Once an artifact is approved and an agent later runs it, the Egress Guard is what watches where it connects.

What it scans for

41 rules across six families, applied to every file in the artifact. A live progress bar shows the scan file by file, so a large plugin doesn't just sit on a spinner:

  • Outbound network. fetch, axios, curl, wget, WebSocket — anything that can move your data off the machine.
  • Code execution. child_process, exec, spawn, eval, new Function, subprocess — the shell-out and dynamic-eval primitives.
  • Credential reach. process.env, keytar and the OS keychain, ~/.ssh, ~/.aws, .env, credentials.json — a “formatting” skill that reaches for your keychain or your cloud credentials has told you what it is.
  • Destructive & out-of-workspace filesystem writes. rm -rf, fs.unlink/rmSync, shutil.rmtree, Remove-Item — and, separately, any write whose path leaves the workspace (~/, ..\/, $HOME, %APPDATA%, an absolute system path). Either is worth a look; together on one line — a destructive op pointed outside the project — they are the shape of a skill that deletes something it was never asked to touch.
  • Obfuscation. Long base64 or hex blobs, atob piped into an exec, minified single-line payloads — the shapes whose whole purpose is to stop you reading the file. A base64 decode on its own is benign (images, fixtures); it turns red when the same file also contains something that would execute what the decode produces.
  • Prompt injection in the instructions themselves. The dangerous part of a skill is often not its code but its prose: text engineered to hijack the agent that reads it (“ignore your previous instructions, then exfiltrate…”). Safe Import treats the skill's own instruction files as an attack surface — because to your agent, that is exactly what they are.

The report, and the red line

You get a red / yellow / green report listing every finding with its rule, file, and line — so you decide on evidence you can go and read for yourself, not on a score out of ten.

  • 🟢 Green — nothing matched. Install it if you want it.
  • 🟡 Yellow — it matched something that legitimate code also does (a skill that calls an API genuinely does need fetch). Your call, with the offending lines in front of you.
  • 🔴 Red — cannot be installed. Not “warns you loudly”. Cannot. The refusal is enforced in the main process, independently of the UI, so a red verdict is not a dialog you can click past and not something a renderer bug can wave through.

Approval is pinned to the content, not the name

When you approve an artifact, the approval is pinned to a SHA-256 of the artifact's content — not to its name and not to its path. If the artifact is later edited, the hash no longer matches and you are prompted again. That closes trust-on-first-use-then-swap: a skill cannot pass review as something harmless, settle into your config, and then quietly become something else.

Where an approved artifact lands

On approval — and only on approval — Termpolis installs it into the agent configuration it belongs in. Which agents an artifact reaches depends on its kind, because the three CLIs do not all implement the same extension points:

  • MCP server → all three agents. Written into the MCP config for Claude, Codex and Gemini. MCP is the one extension point all three speak.
  • Slash command → Claude and Gemini. Both have a user-command directory (~/.claude/commands and the Gemini equivalent). Codex does not, so a command is not installed for Codex.
  • Skill, subagent, or plugin → Claude Code only. (~/.claude/skills, ~/.claude/agents, and the plugin directory.) Claude Code is the only one of the three with a skills/subagent/plugin system, so there is nowhere else for these to go.

So “Safe Import wires it into your agents” is deliberately not a blanket claim: an MCP server really does reach all three, while a skill only ever reaches Claude. The report tells you which agents a given artifact will actually touch before you approve it.

The installer itself is hardened against the two classic archive attacks: zip-slip (an archive entry whose ../../ path escapes the extraction directory to write somewhere it was never meant to reach) and TOML injection (an artifact whose fields are crafted to break out of the field they belong in and inject extra configuration into an agent's MCP file). Both are refused — not sanitised, patched up, and hoped over.

Copy for Slack / Teams / PRs

AI workflows generate share-worthy output constantly: a stack trace to drop into a channel, a clean test run for a PR, a fenced code block for the engineering wiki. Termpolis gives you three reserved copy shortcuts. They are always on and cannot be reassigned — a custom keybinding can never shadow them — so the path from terminal to a teammate is a single keystroke.

Copy — Ctrl+Shift+C

Copies the current selection — or the entire visible terminal buffer if nothing is selected — as clean text. This is the everyday copy, permanently bound to Ctrl+Shift+C.

Copy for Teams/Slack — Ctrl+Shift+K

Formats the output to paste cleanly into Slack and Microsoft Teams: a compact, emoji-friendly message with no hard-wrapped line breaks, sent as an ordinary chat message rather than a giant code box that Slack and Teams would otherwise mangle.

Copy as Code Block — Ctrl+Shift+Q

Wraps the selection, or the whole visible buffer, in triple-backtick fences and copies it to the clipboard. Markdown-ready for GitHub, GitLab, or any wiki that respects fenced code, and the monospaced layout survives.

These three shortcuts are reserved defaults: they always work and a custom keybinding can never override them. Rebind everything else under Settings → Keybindings.

Selecting across scrollback

Dragging to select is fine for what is on screen, but miserable across hundreds of lines — you hold the button down and fight the auto-scroll the whole way. So anchor the two ends instead: Alt + Shift + Click a start point, scroll as far as you like, then Alt + Shift + Click the end. Everything between is selected and copied in one go.

The anchor is pinned to the text, not to the viewport, so scrolling between the two clicks never moves it — which is the entire point. Click the end before the start if you prefer; the span is the same either way. Any plain click abandons a pending anchor, and ordinary drag-select still behaves exactly as before. Prefer the keyboard? Ctrl + Shift + Space enters copy mode (arrows move, Shift extends, Ctrl jumps by word, Enter copies, Esc exits).

Installation

Download the latest build from the downloads page or GitHub Releases.

PlatformFileSigned
Windowstermpolis-setup-<ver>.exeCode-signed
macOS (Apple Silicon)termpolis-<ver>-arm64.dmgNotarized
macOS (Intel)termpolis-<ver>.dmgNotarized
Linux (Debian / Ubuntu)termpolis_<ver>_amd64.deb
Linux (other distros)termpolis-<ver>.AppImage

Installing the .deb on Debian / Ubuntu

Use dpkg, not sudo apt install ./termpolis*.deb. On Ubuntu 22.04+ apt drops to a sandboxed _apt user that can’t read files in your home directory, which fails with “Permission denied / pkgAcquireRun: 13”. dpkg doesn’t drop privileges, so it works regardless of where the .deb is saved:

sudo dpkg -i ./termpolis_*.deb

The package’s postinst (v1.11.30+) takes care of the rest automatically: it runs apt-get install -f -y to pull any missing transitive dependencies (libgtk, libnss3, …), refreshes the desktop and hicolor icon caches so the launcher icon shows up immediately, and ships the .desktop entry with --no-sandbox --disable-gpu baked into the Exec= line so the dock icon launches a working window on NVIDIA / Wayland setups without any manual flags.

If Windows Defender quarantines Termpolis after install or auto-update

Termpolis is code-signed with an SSL.com OV certificate. Microsoft Defender’s cloud ML classifier occasionally false-positives newly-released Electron apps that haven’t yet accumulated SmartScreen reputation, typically flagging Termpolis.exe as Trojan:Win32/Cinjo.O!cl or a similar !cl (cloud) heuristic. Symptoms: the app refuses to launch, its taskbar / Start Menu / Desktop shortcuts disappear, and the installed Termpolis.exe shows blank version metadata.

To recover, open PowerShell as Administrator and run:

Add-MpPreference -ExclusionPath "$env:LOCALAPPDATA\Programs\Termpolis"
Add-MpPreference -ExclusionPath "$env:APPDATA\termpolis"
Add-MpPreference -ExclusionPath "$env:LOCALAPPDATA\termpolis-updater"
Add-MpPreference -ExclusionProcess "Termpolis.exe"
& "$env:ProgramFiles\Windows Defender\MpCmdRun.exe" -Restore -Name "Trojan:Win32/Cinjo.O!cl" -All
Start-Process "$env:LOCALAPPDATA\termpolis-updater\pending\Termpolis.Setup.*.exe"

The first four lines tell Defender to stop scanning Termpolis (preventing the same FP on future auto-updates). The -Restore line pulls any already-quarantined Termpolis files back out. The final line re-runs the most recent pending installer to repair anything that went missing.

If you prefer the GUI: Windows Security → Virus & threat protection → Protection history, click the Termpolis detection, choose Actions → Allow on device, then reinstall from the downloads page. Either way, please also submit the binary as a false positive to Microsoft — that’s what builds reputation and stops it happening to other users.

If your machine is corporate-managed (Intune / Tamper Protection locked)

On Azure AD-joined or Intune-managed devices, the steps above may silently fail — including Allow on device in Protection History. You can confirm this by running in PowerShell:

(Get-MpComputerStatus).TamperProtectionSource

If that returns Intune (or EnterpriseClient), your IT department’s policy outranks local admin for any Defender modification. Open a ticket with your IT/security team and ask them to add one of:

  • A per-machine folder exclusion for %LOCALAPPDATA%\Programs\Termpolis, %LOCALAPPDATA%\termpolis-updater, and %APPDATA%\termpolis.
  • A process exclusion for Termpolis.exe.
  • (Most durable) A publisher exclusion for code signed by CN=David Engelhart, O=David Engelhart, L=Savannah, S=Georgia, C=US — covers every future Termpolis release without per-version updates.

Until IT processes the exclusion, the working stop-gap is to install an older release that hasn’t been classified by Defender’s cloud model: browse GitHub Releases, pick the version right before the one that got flagged, and disable auto-update in Termpolis Settings so it doesn’t re-pull the flagged build.

Requirements

  • Windows 10 or 11 (x64)
  • macOS 10.15 (Catalina) or later, Apple Silicon and Intel builds ship separately
  • Linux: .deb for Debian/Ubuntu, AppImage for any other modern glibc distro
  • ~200 MB disk; 512 MB RAM minimum, 2 GB recommended when running multiple agents

Data directory

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

Before you begin · API keys

Termpolis is a terminal — it doesn't talk to AI providers itself. Each AI agent (Claude Code, Codex, Gemini CLI) is a separate CLI tool you launch inside a Termpolis terminal, and that tool needs its own credentials. Termpolis never asks you for an API key, never sees one, and never stores one.

The minimum to be productive: pick one provider, install its CLI, set its API key once. You do not need all three. A single agent is enough to use every Termpolis feature except the multi-agent swarm.

Pick the agent that matches the account you already have

If you have…Use this agentInstall
Anthropic / Claude.ai paid plan Claude Code npm install -g @anthropic-ai/claude-code
OpenAI / ChatGPT paid plan Codex npm install -g @openai/codex
Google AI Studio key, Vertex AI, or Code Assist (Workspace) Gemini CLI npm install -g @google/gemini-cli

Setting the key (one-time, per provider)

Each CLI reads its key from an environment variable. Set it once in your shell's startup file (~/.bashrc, ~/.zshrc, or your PowerShell profile) and Termpolis terminals will pick it up automatically. The exact variable name per provider:

ProviderVariableWhere to get the key
Anthropic (Claude Code) ANTHROPIC_API_KEY console.anthropic.com
OpenAI (Codex) OPENAI_API_KEY platform.openai.com
Google AI Studio (Gemini) GEMINI_API_KEY aistudio.google.com

Example, bash/zsh:

echo 'export ANTHROPIC_API_KEY="sk-ant-..."' >> ~/.zshrc
source ~/.zshrc

Example, PowerShell:

[Environment]::SetEnvironmentVariable('ANTHROPIC_API_KEY', 'sk-ant-...', 'User')

Heads-up about Gemini. The gemini CLI defaults to a free OAuth tier when no env var is set, and Google may use that traffic to improve products. Termpolis flags this in Settings → AI Security and offers a Strict Mode that blocks the launch until you set GEMINI_API_KEY (or another paid-tier signal).

Verifying it worked

Open a Termpolis terminal (Ctrl + Shift + T) and type:

echo $ANTHROPIC_API_KEY     # bash / zsh
$env:ANTHROPIC_API_KEY      # PowerShell

If the value prints, you're done. If it's empty, restart Termpolis after editing your shell's startup file so the new environment is inherited.

First launch (5 steps)

The first time you start Termpolis you'll see a four-step onboarding tour: What Termpolis isSet an API keyLaunch your first agent (or swarm)Security & crash reports (opt-in checkbox). The tour is one-time; you can revisit it any time from Help / Support → Show tour again. After dismissing it you land on the welcome view. From there:

  1. Pick a shell. Click any of the quick-launch buttons in the center of the welcome view — bash, zsh, PowerShell, cmd, or whichever shells Termpolis detected on your machine. A terminal pane opens.
  2. (Optional) Open the AI Agents row. The sidebar has an AI Agents section with one-click launchers for Claude Code, Codex, and Gemini CLI. A green check means the CLI is installed; a red X means it isn't (click for install instructions). Skip this on the first run if you just want to use Termpolis as a terminal.
  3. Open Settings. Click the gear in the top-left of the sidebar. Pick a theme, set your default shell, and look at the AI Security tab if you launch any AI agent. Changes save instantly.
  4. Save your first workspace. When you've got terminals you'd want to come back to, click + Save Workspace in the sidebar. Termpolis snapshots the names, shells, themes, and working directories so they restore the next time you open the app.
  5. Press Ctrl + / any time to jump to the full keyboard-shortcuts list (Settings → Keybindings), or click Help / Support in the bottom status bar for the in-app help drawer that covers every feature and panel.
Termpolis sidebar in default state.
The sidebar as it appears immediately after first launch.

Launch your first AI agent

The fastest way to do something useful with Termpolis is to drive a single agent. No swarm config, no MCP setup, no JSON. Once your API key is in place:

  1. Open the AI Agents section in the sidebar (it's the row above Workspaces, with the robot icon).
  2. Click the agent you want to run — Claude Code, Codex, or Gemini CLI. Termpolis opens a fresh terminal, sets the right working directory, runs the agent's interactive command, and color-codes the tab so you can see at a glance which agent is in which pane.
  3. Type your task in plain English. Examples:
    • "Add a dark-mode toggle to the header component."
    • "Refactor userService.ts to use async/await throughout and add tests."
    • "Find every TODO in src/ and tell me which ones look stale."
  4. Watch the status bar. A colored badge appears next to the agent name with running token cost. The Observability panels light up the moment the agent makes a tool call.
  5. If the agent runs out of context, an amber banner offers a cross-AI handoff — one click to continue the same conversation in a different model with task, branch, and recent diff already in scope.

You don't need to configure MCP, edit any JSON, or set up tooling — Termpolis auto-registers itself with each agent the first time you launch it. See MCP server for what that actually unlocks.

Want to scale beyond one agent? Read Swarm vs. single agent next to decide whether your task is worth orchestrating.

Swarm vs. single agent

Termpolis ships two ways to put AI to work. Pick the one that matches the shape of your task — they're not interchangeable.

Quick decision tree

  • Is the task one tight loop of "try → see → fix"?Single agent. You're the conductor. Open Claude Code (or whichever agent you trust most for this domain) and iterate.
  • Does the task split into independent chunks that can run in parallel? (e.g. "build the API + write the migration + add tests + write docs", or "rewrite three unrelated modules in the same style.") → Swarm. The conductor decomposes the task, hands subtasks to the agents best-rated for each, and merges results.
  • Is the task a long spec rather than a single ask?Swarm. Drop the spec into the Start Swarm wizard and let the conductor plan.
  • Are you exploring or debugging interactively?Single agent. Swarms are autonomous; an interactive back-and-forth is the wrong shape for them.
  • Do you need different agents because they're individually best at different sub-skills (e.g. Codex for tests, Gemini for docs)? → Swarm. The capability ratings drive routing for you.

What you get with each

 Single agentSwarm
Setup Click an agent in the sidebar. Ctrl + Shift + S → fill the Start Swarm wizard.
Best for Iteration, exploration, debugging, learning. Parallelizable specs, multi-skill tasks, autonomous runs.
Cost Just the agent you're using. Conductor + each agent it picks. Set a budget cap in the wizard.
Visibility One terminal pane. Swarm Dashboard (Tasks · Messages · Trace tabs). Agent terminals are hidden by default — the conductor drives them.
You drive Every prompt. The first prompt; the conductor takes it from there.

See AI conductor for the full swarm walkthrough or AI agent profiles for single-agent details.

Workspaces

Workspaces are the project-level container in Termpolis. Think of them as the tabs in a browser, except each one holds a full set of terminals, a split/grid layout, an active agent, a scrollback history, per-workspace settings, and any panels you've left pinned. You can run many workspaces side-by-side and switch between them without losing state.

What a workspace owns

  • Terminals, every open pty in that workspace, with its shell, working directory, label, color, and scrollback buffer.
  • Layout, tab view, split view (the full pane tree), or grid view. Restored exactly on relaunch.
  • Focus, which terminal was active, cursor position, selection.
  • Agent sessions, any Claude Code, Codex, or Gemini CLI runs tied to terminals in the workspace.
  • Panel state, which side panels are open and their size.
  • Per-workspace overrides, any setting scoped specifically to this workspace (shell default, font size, etc.).

How workspaces persist

Everything above is written to session.json in the Termpolis data directory (see Installation for the per-platform path) as soon as it changes, so an unclean shutdown still leaves you with last-known-good state. Re-opening the app restores the workspaces in the same order with the same terminals, split layouts, and focus.

Creating a workspace

Use the + Workspace button at the top of the sidebar or the Ctrl + Shift + N shortcut. Each new workspace starts empty; pick a shell to open the first terminal.

Managing workspaces

Right-click any workspace row in the sidebar for:

  • Rename, changes the label in the sidebar and the window title when the workspace is active.
  • Duplicate, creates a new workspace with the same terminal configuration (shell, cwd, label) but fresh, empty pty sessions. Useful when mirroring a setup for a second feature branch.
  • Close, removes the workspace. If any terminals have live child processes, a confirmation dialog lists what's still running.
  • Show in file explorer, opens the workspace's working directory in Finder / Explorer / your Linux file manager.

Switching between workspaces

Click a workspace row in the sidebar to activate it. Unsaved terminal output in background workspaces keeps streaming, nothing is paused just because it's not visible.

Workspace root directory

Each workspace has a default working directory that new terminals start in. Set it when you create the workspace, or change it later from Settings → Workspace. Terminals started with the agent launcher inherit this unless they override it per-terminal.

Terminals

New terminal modal with shell + agent options.
The new-terminal modal with shell, cwd, and agent profile options.

Every pane is a full pty-backed terminal powered by node-pty, real TTY semantics, xterm escapes, signal forwarding. Not a shim.

Create one with Ctrl + Shift + T. Pick a shell, a working directory, optionally an agent profile, plus a label and color.

A running PowerShell terminal in Termpolis.
A running terminal, copy on selection, 256-color palette, mouse scroll.

Closing a terminal with an active process prompts for confirmation, this protects long-running tasks (model downloads, builds, agent sessions) from accidental loss.

Tabs, splits & grid

Tab view with multiple terminals open.
Tab view, every terminal gets its own tab.

Terminals are arranged in one of three view modes. Pick whichever fits the task, tabs for deep focus, splits for side-by-side work, grid for watching the swarm.

Split view with two terminals side by side.
Split view, recursive, drag the divider to resize.
  • Right-click a pane → Split right / Split down, or use the command palette, to split.
  • Ctrl + Shift + G, toggle split / grid view
  • Alt + 1…9, jump straight to terminal N
  • Ctrl + Shift + W, close the focused terminal

Settings

Settings panel.
The settings panel, slides in from the right.

Open with the gear icon in the sidebar. Changes save immediately, no apply button.

Tabs: General (theme, default shell, autocomplete, auto-primer, telemetry opt-in, and Safe Import), AI Security (including the Commit Shield and the audit-log viewer), Memory & Learning (the local metrics dashboard), Voice, Keybindings, Agent Ratings, and Shell Config.

Themes

Themes picker.
The theme picker, curated dark themes plus VS Code import.

Ships with Termpolis Dark (default), Dracula, Solarized Dark, Nord, Gruvbox Dark, Tokyo Night, Monokai. Each applies to terminals, app chrome, and AI conversation syntax highlighting. Import any VS Code theme JSON, the parser maps tokenColors to xterm colors automatically.

Keybindings

Keybindings settings tab.
Every user-facing action is rebindable.

Bindings are platform-aware, Ctrl becomes on macOS automatically. Conflicts are flagged inline when rebinding.

Voice dictation

Talk instead of type. Transcription uses Groq's hosted Whisper API — the few seconds you dictate are sent to Groq's cloud for transcription, and the text comes back into your terminal. It is opt-in and off by default, and it requires your own Groq API key. (There is no local/on-device engine; voice went Groq-only in v1.13.0.)

Turn it on (one-time setup)

  1. Open Settings → Voice and toggle Enable voice input on (it's off by default).
  2. Click Connect Groq, accept the consent notice (your recorded audio is sent to Groq), and paste a free Groq API key from console.groq.com/keys. The key is validated against Groq before it's saved.
  3. Optionally pick your microphone and hit Test microphone to confirm the level meter jumps when you speak, and choose the transcription model.

Your API key never enters the renderer. It's stored encrypted in your OS keychain (Windows DPAPI, macOS Keychain, Linux libsecret) and used only in the main process to call Groq — it never lands in session.json, settings, or any log. The renderer only ever sees a masked hint (gsk_••••abcd) and a connected flag.

How to dictate

  • Tap Ctrl + Shift + L to start, tap again to stop — or hold it to talk and release to send. Both work on the same key by default (tap-or-hold). Pure-hold, tap-to-toggle, and tap-then-press-a-key-to-send modes are selectable under Activation; the hotkey and the send key are both rebindable. You can also click the mic button on the terminal pane, or the Listening… badge, to start/stop.
  • In an AI-agent terminal (Claude · Codex · Gemini) your words are sent straight to the agent as a prompt — it absorbs minor mis-hearings, so just talk naturally. (Auto-submitting that prompt is an opt-in toggle, off by default, so you can still review before pressing Enter.)
  • 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 — keep typing or start another dictation without clicking back in.

Accuracy, no-speech handling & privacy

  • Tuned for English. Groq's model defaults to whisper-large-v3-turbo; you can switch to whisper-large-v3 in Settings → Voice.
  • Never types a phantom word. Termpolis energy-gates your audio before sending and backstops the reply, so background noise or silence never reaches Groq and a hallucinated filler is never injected — you get a “No speech detected” notice with the measured mic level instead. If that keeps happening, pick the right mic and run Test microphone.
  • Only what you dictate leaves the machine, and only to Groq. By default Groq does not train on or retain it; for the hardened setup, enable Zero Data Retention in your Groq console (the Connect dialog links you there). The free tier covers everyday use; paid is roughly $0.04 per hour of audio. If a transcription fails, a red error bar says exactly what went wrong (most often a missing/invalid key or no internet).

Agent capability ratings

Agent capability ratings panel.
Score each agent across 10 categories, powers the swarm router.

The heart of smart swarm routing. Score each agent (Claude Code, Codex, and Gemini CLI) from 1–5 (5 = strongest) across 10 categories: Refactoring, Architecture, Testing, Documentation, Code Review, Debugging, Frontend, DevOps, Data Analysis, and Bulk Tasks. Defaults reflect model-family strengths; tune them to match your experience under Settings → Agent Ratings.

A per-agent Token Cost indicator (Free / Low / Medium / High) drives cost-aware routing, so the conductor can hand token-heavy bulk work to a cheaper agent.

Command palette

Command palette open.
Ctrl + K, the fastest way to do anything.

The palette is your keyboard-only shortcut to every action in Termpolis. Actions, workspaces, recent commands, files, all searchable with fuzzy matching, exact matches floating to the top.

Command palette filtered by 'launch'.
Type to filter, "launch" surfaces every agent and terminal launch action.

Why use it

  • Don't remember keybindings? Type the feature name, the palette shows the shortcut next to the action.
  • Jump across workspaces in two keystrokes. Ctrl + K, type workspace name, Enter.
  • Re-run a recent command anywhere. The palette surfaces your last 50 commands across every terminal, type a fragment, hit Enter, and it runs in the focused pane.

How to use it

  1. Press Ctrl + K.
  2. Type a query, e.g. split, new terminal, workspace, launch claude.
  3. Use / to select; Enter runs it; Esc closes.

Prompt templates

Prompt templates library.
Reusable prompts with variable substitution, Ctrl + Shift + P to open.

Save the prompts you type over and over. Each template supports {{variables}} that get filled from your current selection, cwd, or a free-form input when you launch it.

Built-in templates

  • Explain this code, pastes current selection as context, asks the agent to walk through it.
  • Write tests, generates unit tests for the focused file.
  • Refactor for readability, rewrites with a focus on clarity, not behavior changes.
  • Find security issues, static-analysis-style review for OWASP-top-10 patterns.
  • Document this API, produces JSDoc / TSDoc / docstrings for the selection.
  • Code review, strict, adversarial review with a "what would break?" lens.

How to use it

  1. Select the code you want to discuss in any terminal (or leave empty to use free-form input).
  2. Press Ctrl + Shift + P.
  3. Pick a template, Termpolis fills {{selection}}, {{cwd}}, {{branch}}, etc. automatically.
  4. Fill any remaining prompts in the form, then click Send to active agent.
  5. The fully-expanded prompt is written to the focused agent's stdin.

Customizing

Click + New in the template picker or edit prompt-templates.json in your data directory directly. Each entry is {id, label, body, variables, defaultAgent}.

Workflow Orchestrator

Workflow Orchestrator designer with Command, Agent, Skill, and Control steps.
The designer — a vertical pipeline of typed steps, each editable inline.

The Workflow Orchestrator chains commands, AI agents, and built-in tools into one repeatable, saveable run. Open it from the Workflows section in the sidebar: press the inline + (Start Workflow), name it, then add steps with the inline + button. A project workflow is stored as portable YAML in the project itself, under .termpolis/workflows/ — commit it alongside your code and the whole team gets the workflow.

The four step types

  • Command — run a shell line on a real PTY. Inline or from a script file, any shell (bash, zsh, PowerShell, cmd, git-bash), with an optional timeout and a “run visibly” toggle that surfaces it in a live terminal pane.
  • Agent — launch Claude Code, OpenAI Codex, or Gemini CLI on a prompt and wait for it to finish; a configurable done-marker detects completion.
  • Skill — call one of Termpolis’s built-in tools (code search, memory, git status…) with JSON arguments, in-process, no terminal required.
  • Controlwait, branch, loop, or notify: the flow control that turns a list of steps into a real program.

Gates, control flow, and data

Every step carries a 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 with a fixed operator set (==, !=, &&, ||, comparisons, contains); never eval, so a workflow file can never execute arbitrary JavaScript.

Workflow Orchestrator run view: a live timeline of steps going green with elapsed times.
The Run view — each step streams live, then settles to succeeded, failed, or skipped.

Running it

Switch to the Run tab and press Run. The timeline shows every step’s status and duration in real time; Command and Agent steps stream their output into a pane you can scroll. Press Cancel mid-run and every in-flight step is torn down cleanly. Runs are deterministic — the same workflow and inputs take the same path every time.

Triggers — running it automatically

A workflow can start itself. Pick a trigger at the top of the designer:

  • Manual — the default; only the Run button starts it.
  • Schedule — a standard five-field cron expression (0 2 * * * = 02:00 daily; */15 * * * * = every 15 minutes), or an alias: @hourly, @daily, @weekly, @monthly, @yearly. Times are your machine’s local time. If the app was closed when a run was due, it catches up once on the next launch — untick “Run at launch if it was due while Termpolis was closed” to skip missed runs instead. An invalid expression is never armed, so it can never fire.
  • Git commit — fires just after a commit lands on the checked-out branch. This is a post-commit hook, not a pre-commit one: it cannot block the commit, but it can lint, run the tests, or hand the new diff to an agent. Leave Branch blank to follow whichever branch is checked out (switching branches re-seeds rather than firing), or name one to watch only that branch.
  • Git push — fires when the remote-tracking ref moves (refs/remotes/origin/<branch>), i.e. after a successful push or fetch. Set Remote for a remote other than origin.
  • File change — a recursive watch of the project folder, debounced (Debounce (ms), default 2000) so a burst of saves is one run. Paths takes comma-separated prefixes (src/, docs/) to narrow it; blank watches the whole project. Noise directories — node_modules, .git, dist, coverage, and Termpolis’s own .termpolis — are always ignored, so a workflow can never retrigger itself.

Triggers are watched inside Termpolis — there is no daemon, no installed git hook, and nothing runs when the app is closed. Last-seen state is persisted per project (.termpolis/workflows/.triggers.json), so restarting does not replay a commit you already handled. An automatic run is identical to pressing Run: same steps, same run history, same live timeline — and the same workspace-trust gate, so a folder you have not trusted never fires one. Only one run of a given workflow is in flight at a time; a trigger that fires during a run is applied once the run settles.

Global workflows, categories and inputs

A workflow does not have to belong to one repo. In the designer, Availability switches it between This project (saved in .termpolis/workflows/, travels with the code) and Global (saved once in your Termpolis data directory and offered in the sidebar in every project you open). Switching moves the file — it never appears twice. A global workflow always runs in the project you are standing in, so one definition can lint, test or release whichever repo you are working in. Its triggers arm per project too: a nightly cron saved once fires once per repo you have open, each with its own last-seen state.

Category is a free-text label that files the workflow into a collapsible folder in the sidebar — global and project workflows are grouped separately, each with its own folders.

Inputs are what make one workflow serve many targets. Declare them in the designer (name, prompt label, default, required) and they are collected in a small form on the Run tab before the run starts — a required input left blank keeps the Run button disabled rather than starting a run that cannot work. Every value is substituted as ${inputs.NAME} anywhere the expression engine reaches: command lines, script paths, step cwd, agent prompts, skill arguments, notify messages and when gates. Alongside them, every run gets ${project.cwd}, ${project.name} and ${project.branch} for free, so a workflow can branch on where it is running without being told. Resolution is still sandboxed — no eval, and an unknown reference collapses to an empty string rather than leaking into a command line.

Context panel

Context panel.
Ctrl + Shift + E toggles it.

The Context panel is Termpolis's answer to the "what does the AI actually know right now?" question. It's a sliding pane on the right that shows everything currently in scope for the agents you're working with, so you never have to guess whether Claude saw the file you just edited, or whether Codex knows which branch you're on.

What it shows

  • Project root + git branch, the cwd of the focused terminal and the active branch, so an agent can ground its answers in the right repo state.
  • File tree, collapsible directory view of the project, click to pin a file into context.
  • Active agent, which AI is focused and its model.
  • Recent files, last 20 files the agent (or you) read, edited, or ran. Click to re-open in Monaco's diff view.
  • Pinned items, anything you've pinned: a file, an activity event, a prompt, a memory entry. Pins stay visible across sessions.
  • Task chain, when a swarm is running, the current task, its parent, and its dependencies.

Why it's helpful

  • No more "please re-share the file" loops. Pin it once and every agent can read it.
  • Explicit context, not implicit slurp. Agents only see what you've put here, no silent indexing of your filesystem. Safer for private code.
  • Share context across agents. Pin a spec once and Claude, Codex, and Gemini all read from the same truth.

How to use it

  1. Press Ctrl + Shift + E to open.
  2. Click File tree to browse; right-click a file → Pin to context to make it permanent.
  3. Right-click any activity feed event → Pin to float it to the top.
  4. When an agent asks "what am I looking at?", it reads from this pane directly via MCP.
  5. Click the × on any pin to remove it; pins are saved per directory under context-pins/ in the Termpolis data directory.

Token Headroom — compressing what the agent sends

Long sessions hit rate limits and compaction fast, and the biggest cost isn't your prompts — it's the file reads, command output and pasted screenshots the agent ships back to the model on every turn. Token Headroom compresses that traffic before it leaves your machine.

It is always on for Claude Code and needs no setup. Every Claude session launches through a local compression proxy running in its own process — never the UI or PTY thread. If the proxy is ever unhealthy the launch silently goes direct, and a live session self-heals when it recovers, so a proxy hiccup costs you compression, not your agent.

What gets compressed

  • Source code is outlined, not truncated (v1.35). Imports, declarations and every class/function signature survive; bodies collapse to … 24 lines …. You keep the API surface of the whole file instead of the first and last few lines of it. Roughly 35 languages, brace- and indentation-delimited alike. It sees through the cat -n gutter on a Read result so line numbers stay correct, and refuses to fire when the text isn't code or the outline wouldn't be meaningfully smaller.
  • JSON is compressed as JSON (v1.35). Long arrays keep their first entries and state how many were elided, long string fields are truncated in place, deep nesting is pruned, and the survivors are re-emitted minified. It refuses the payload entirely when any numeric literal is long enough to lose precision in a round trip — a corrupted ID the agent believes is worse than an uncompressed one.
  • HTML is reduced to its readable text. Fetched web pages lose their markup, not their content.
  • Command output and large file reads are bounded by a head/tail line window, with structured content given the budget of prose.
  • Pasted images are downscaled below the model's cap and emitted as PNG or JPEG, whichever is smaller.
  • Repeats and near-repeats. A result repeated in the same session collapses to a one-line reference. Since v1.34, a near-duplicate is sent as a patch against the earlier copy: read a file, edit three lines, read it again, and only those lines go over the wire.
  • Both halves of the wire (v1.34). Not just the tool result that came back, but the tool_use the agent itself wrote — the 400-line body in a Write, the large Edit payload — which is re-read out of the prefix on every later turn. Identifier fields (file_path, command, pattern, url) are excluded by name so a call never becomes ambiguous.
Nothing is lost. Every compressed block carries a token the agent can hand to the retrieve_full tool to pull the original back. Since v1.34 that cache is disk-backed, byte-capped and survives restarts, so a token issued yesterday still expands today.

Why it doesn't bust the prompt cache

Naive compression costs you money: change one byte of the prefix and Anthropic's prompt cache misses, so the whole conversation is re-ingested at full price. Headroom compresses deterministically — stash tokens are content hashes rather than counters, so the same block always yields the same token no matter what came before it, and there is no clock or randomness anywhere in the compression path. A dedicated test suite asserts the compressed prefix is byte-identical across ten successive appends, when a duplicate arrives, when a near-duplicate is diffed, and at the hardest tier.

Reading the receipt

Settings → Token Savings reports against three denominators rather than picking the flattering one. All three are computed from Anthropic's real usage numbers on your machine.

DenominatorTypicalWhat it means
Compressible wire text~61%Of the tool text the compressor actually sees. The measure of the compressor itself.
All input tokens~9%Includes the cached prefix that is re-read every turn.
Effective cost~6%After cache-read and cache-write weighting. What it saves you in money.

The last two are small by arithmetic, not weakness: compressible tool text is only 42.3% of a request body, so removing all of it would still cap out at 42.3%. The panel also shows worst-request and below-floor counts, prompt-cache health, and the share of effective spend that is output rather than input. It sums both compression surfaces — the wire proxy and Termpolis's own MCP tool output — into a single gross/net/give-back figure.

The numbers, and where they come from

4,183 real requests captured from one developer's own transcripts — 35.2 GB of request bodies — were replayed through the compressor. At the shipped default: 61.0% of compressible tool text removed (63.5% of tool results, 51.6% of tool_use payloads), median request 61.4%, and 93.8% of requests clear 50%. At the Maximum tier, 72.3%, with 99.9% of requests clearing 50%. Cache-safety was measured over 62,716 real requests with hits fully preserved. A regression test fails the build if any of these thresholds is ever loosened.

Optional controls (off by default)

  • Prefix decay. On very long conversations the oldest half can be aged down to retrievable stubs. It ships off because it is the one control that can cost you money: shortening the prefix forces a re-cache (~1.15× the prefix), which needs roughly 39 more turns to repay at typical volumes. The cutoff advances only at doublings (64 → 128 → 256 messages), so a 300-turn session pays a handful of breaks rather than one per turn.
  • Thinking-budget cap. Output — thinking included — bills at roughly 5× input and was 38% of measured effective spend. The cap lowers an over-declared budget to a fixed per-session value (constant by design, or it would bust the cache). It trades reasoning depth, so it ships off.
  • Compression tier. Conservative, balanced, aggressive and maximum. Aggressive is the shipped default.
Your memory is never touched. Compression lives only on the outbound wire to Anthropic. The shared memory store, recall and learning are untouched by it.

Cross-AI context handoff & Past AI Sessions

Open Past AI Sessions from the sidebar. Termpolis indexes every ~/.claude/projects/**/*.jsonl transcript on your machine, shows them with project label, age, message count, and size, and lets you continue the work in any of the three supported agents — without copy-pasting anything by hand. (v1.11.47 added the cross-AI bridge; v1.11.48 fixed the freeze on large session histories; v1.11.51 added Inject + Resume-here gating; v1.11.52 made the inject paste land as a single bracketed-paste blob.)

What you can do with a past session

  • Resume natively — spawns a new terminal at the original cwd and runs claude --resume <session-id>.
  • Resume in active shell — runs the same claude --resume command in the focused plain terminal instead of opening a new tab. Disabled when the focused tab is already an AI agent (so the command doesn't land inside the running agent's input box).
  • Continue in another AI — pick Codex or Gemini from the Continue ▾ menu. Termpolis spawns a fresh terminal at the same cwd, boots the chosen agent, and pastes a synthesised CONTEXT HANDOFF prompt summarising what's been done so the new agent picks up exactly where the old one left off.
  • Inject context into the focused AI — pastes the same handoff prompt into the active AI shell's input box. Disabled for plain shells (where it would just splat into the command line); the tooltip explains why.

Pasted as one blob, not a stream of commands

The handoff prompt is wrapped in xterm bracketed-paste markers (ESC[200~ … ESC[201~) and its embedded newlines are normalised to \r so the receiving agent treats the whole thing as a single paste event rather than dispatching one keypress / submit per embedded line. Result: the agent sees one coherent context block, not what previously looked like a flurry of random commands.

History search

History search panel.
Search every command you've ever run in Termpolis.

Spans every terminal Termpolis has ever opened, not just the current shell's history file. Filter by command, cwd, exit code, time range, or shell type. Click a result to copy, re-run, or pin to context.

Conversation search

Conversation search panel.
The AI-session equivalent of history search, find any prompt, tool call, or answer across every agent you've ever run.

Open with Ctrl + Shift + I. Every AI agent session in Termpolis, Claude Code, Codex, Gemini CLI, swarm runs, and the conductor itself, is recorded event-by-event to agent-events.jsonl in the Termpolis data directory. Conversation search is the UI on top of that log.

What's indexed

  • Prompts, every user message sent to an agent.
  • Assistant messages, the full reply text, plus intermediate thinking blocks where available.
  • Tool calls, the tool name and arguments (e.g. Edit, Bash, WebSearch).
  • Tool results, the response the agent got back (stdout, file contents, etc.).
  • Errors, anything the agent raised or the pty emitted on stderr.
  • Token updates, periodic snapshots of cumulative input/output tokens.
  • Interventions, every Pause / Cancel / Steer action you took, who took it, and when.

Filters

  • Full-text, matches prompt text, assistant replies, and tool arguments.
  • Agent, narrow to Claude, Codex, Gemini, Conductor, or "all".
  • Kind, prompt / tool_call / tool_result / error / message.
  • Time range, last hour, today, last 7 days, or custom.
  • Scope, current terminal only, current session, or all history.

Why it's helpful

  • "How did I fix this last time?", find the exact prompt and solution from a month ago.
  • Audit what an agent actually did. When something breaks, grep the tool-call log, every Edit, every Bash, every file touched.
  • Reuse successful prompts. Click a hit → Copy prompt to rerun it in a fresh session.
  • Compare agent answers. Search the same question across agents to see how Claude, Codex, and Gemini differed.

How to use it

  1. Press Ctrl + Shift + I.
  2. Type a query, e.g. "regex for ISO dates", "docker-compose error", "why did claude say no".
  3. Narrow with the kind + agent dropdowns if the hit list is noisy.
  4. Click a result to expand the full event, prompt and answer side by side.
  5. Use the Copy prompt, Re-run in new session, or Pin to context actions from the hit's kebab menu.

Git panel

Git panel.
Staged/unstaged, inline diff, AI-drafted commit messages.

Current branch + ahead/behind, staged & unstaged sections with inline diff, last 50 commits as a graph, actions for commit/push/pull/fetch/stash/branch switch. Click the ✨ next to the commit-message input and an agent drafts a message from the staged diff.

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.

AI agent profiles

An agent profile is a one-click launcher in the sidebar that opens a terminal pre-wired for a specific AI CLI: Claude Code, Codex, or Gemini CLI. The profile owns the shell, the working directory, the MCP registration, the tab color, and the label. Click the profile and you go from "I want to use Claude" to "Claude is open and ready" in one step.

New here? Read Before you begin · API keys first to set the right env var, then follow Launch your first AI agent for the 5-step walkthrough.

What ships out of the box

  • Claude Code — runs claude --dangerously-skip-permissions in interactive mode so the swarm conductor can drive it via MCP.
  • Codex — runs codex --full-auto.
  • Gemini CLI — runs agy, the Antigravity CLI (Gemini’s current headless entry point); Strict Mode still guards a manually-typed gemini (see Gemini account-mode).

Pick a model per profile (Claude)

When you add or edit an AI profile, a Model dropdown lets you choose which Claude tier that profile launches with — Fable, Opus, Sonnet, or Haiku — appended to the launch command as claude --model <tier>. You pass the alias, not a pinned version number, so you always get the newest model in that tier automatically (for example, the latest Opus): Termpolis never hard-codes a version, so a newer release is picked up with no app update. Cheaper tiers show their token savings so you can trade cost for capability, and the same alias drives the subtasks the conductor assigns in a swarm. Leave it on default to run whatever Claude Code is already configured to use. The Model dropdown applies to Claude Code only — Codex and Gemini run their own default models.

Install status indicators

Each profile shows a green check if Termpolis can find the CLI on your PATH and a red X if it can't. Click a missing one for the npm install command. The check runs every launch — install the CLI, restart Termpolis, the indicator flips green.

Custom profiles

Click + in the AI Agents row to add a profile for any other CLI. You name it, point it at the binary (anything in PATH works), pick a shell, color, and optional working directory. Custom profiles don't get MCP auto-registration, so they participate as plain terminals — fine for tools that don't speak MCP.

Renaming an agent terminal

Agent terminals get default names ("Claude Code", "Codex", etc). Right-click the terminal tab to rename it, change its color, or swap themes — the underlying agent stays put.

Second Opinion

From any AI terminal, hand the most recent answer to a different agent for a quick, read-only critique. Its feedback is pasted back into the same terminal as an unsent block — you decide whether to send it to your primary agent or just read it and move on.

Every model has blind spots. Second Opinion lets a second model sanity-check the last solution before you act on it — e.g. drive Opus and ask Fable, or have Codex review what Gemini just proposed.

How to use it

  • Open the Second Opinion… dropdown at the top-right of any AI terminal (it appears whenever an agent is running there).
  • Pick a reviewer. The menu lists only the agents you have installed — OpenAI Codex, Gemini — with Claude and its models (Fable, Opus, Sonnet, Haiku) nested underneath.
  • Read the feedback. Termpolis captures the terminal's recent output, runs the chosen agent over it, and pastes a concise review back into the terminal — unsent, so you stay in control.

What it does under the hood

  • Read-only & safe. A review never needs file access, so it runs the agent in one-shot headless mode — nothing it says touches your repo. The captured text is passed out-of-band (never on a command line), so a prompt scraped from your terminal can't inject a command.
  • Install-gated. Only installed agents appear. Gemini is accessed through the Antigravity CLI (agy), its current headless entry point.

MCP server

What MCP is, in plain English

MCP (Model Context Protocol) is the channel that lets an AI agent do things instead of just print things. With MCP wired up, when you ask Claude "open a new terminal in the repo root and run the test suite," it can actually do that — open a real Termpolis terminal, type the command, and stream the output back into the conversation. Without MCP, the agent could only suggest the command and ask you to copy/paste it.

If you're launching agents from inside Termpolis, you don't have to do anything. Termpolis auto-registers itself with each of the three supported agents on first launch. The rest of this section is for people who want to understand what's running, debug an integration, or wire up a custom MCP client.

How it's wired up

Termpolis runs an MCP server on http://localhost:9315 (the port is shown in the bottom status bar — if 9315 is taken, Termpolis falls back to the next free port and writes the actual port to the data directory). It's bound to the loopback interface only and rejects any request without the right bearer token, so nothing on your network can reach it.

Connecting a custom client

Any MCP-aware client can talk to the server. Register it once in the client's MCP config — Termpolis writes a fresh bearer token to mcp-token in the data directory (Windows: %APPDATA%\termpolis\, macOS: ~/Library/Application Support/termpolis/, Linux: ~/.config/termpolis/) on every launch:

{
  "mcpServers": {
    "termpolis": {
      "url": "http://localhost:9315",
      "headers": {
        "Authorization": "Bearer <contents of mcp-token>"
      }
    }
  }
}

The 34 tools an agent can call

Each entry below is a function call an MCP-aware agent makes through the protocol — not a command you type. Every invocation shows up in the Activity Feed. The code-graph tools (new in v1.20.0) are backed by a local, native-free symbol index Termpolis builds from your workspace on a background timer — see the Code graph section for the full language list and the in-app browser.

ToolWhat the agent does when it calls this
Terminals
list_terminalsEnumerate open terminals — find an existing session to work in.
create_terminalSpawn a new terminal with a chosen name, shell, and working directory.
run_commandRun a one-shot command in a terminal (used to start a long-running agent CLI).
write_to_terminalType arbitrary text or control chars into a terminal's stdin (this is how the conductor sends task prompts to other agents).
read_outputRead the recent output buffer from a terminal.
close_terminalKill the underlying pty and remove the terminal.
Project context
get_file_treeWalk the workspace's working directory and return a JSON file tree.
get_git_statusSummarise the active repo: branch, ahead/behind, staged + unstaged files.
Swarm coordination
swarm_send_messagePost an inter-agent message (broadcast or directed). Visible in the Messages tab of the Swarm Dashboard.
swarm_read_messagesRead the message stream — agents poll this to check for handoffs.
swarm_create_taskCreate a task record in the swarm DAG (called by the conductor for every subtask).
swarm_list_tasksEnumerate tasks with status filters (pending / in-progress / completed / failed).
swarm_update_taskMark a task complete or failed and attach a result summary.
swarm_list_agentsList the running swarm agent terminals so the conductor can route work.
Shared memory
memory_writePersist a labeled memory entry into the shared memory store.
memory_searchRAG search (semantic + keyword) across shared memory.
memory_listList recent memory entries with filters.
memory_relatedUndirected one-hop traversal — from a memory entry (or a query) to its neighbours, blending its typed graph edges with nearest semantic neighbours (undirected since v1.23).
memory_auditAudit the integrity of the memory store — report what is held, what has been archived or superseded, and whether the on-disk store is internally consistent.
memory_linkRecord a typed edge between two memories — builds the knowledge graph.
memory_graphMulti-hop walk of the knowledge graph from a seed memory.
memory_primerLoad a ranked background-memory digest for the current project at session start.
memory_anticipateBefore solving a task, surface the procedural lessons the fleet already found for it — the anti-re-derivation tool.
memory_poolPool the cross-agent lessons two or more agents independently arrived at — the fleet's most-corroborated knowledge.
memory_selfcheckReport the brain's calibrated self-competence in a domain (confidence + track record) so the agent knows what it knows.
memory_feedbackMark a recalled memory as helpful so repeatedly-useful entries rank a little higher for everyone.
memory_conflictsSurface pairs of lessons that different agents learned that assert opposite things about the same subject, so the fleet’s disagreements can be found and resolved. Read-only and deliberately high-precision.
Code graph (local symbol index — new in v1.20.0)
code_exploreAsk one structural question and get the matching symbol’s verbatim source plus its direct callers and callees — instead of grepping and reading files. Backed by a pre-indexed local code graph.
code_callersList the symbols that call a given symbol — “who uses this?” — straight from the code graph, no grep.
code_calleesList the symbols a given symbol calls — “what does this depend on?”
code_impactBlast radius: the transitive set of symbols that directly or indirectly call a symbol — what could break if you change it. Run it before editing a shared function.
code_searchFind symbols (functions, classes, types) whose name matches a substring across the indexed codebase — a fast structural lookup.
code_locatePredict where an issue lives — give it an error or problem description and get back a ranked list of {file, symbol, why:[past lessons]}, the code most likely responsible with the fixes/decisions that point there. Crosses the memory↔code bridge (new in v1.23.0).

Security

  • Bearer token. A random 256-bit token is generated per app launch and written to mcp-token in the data directory. Every request must include it in the Authorization header.
  • Loopback only. The server binds to 127.0.0.1 and refuses non-loopback connections.
  • Origin checks. Requests from browser origins are rejected unless explicitly allowlisted.
  • Rate limits. Per-client rate limiting protects the host from a runaway agent loop.
  • Audit log. Every tool call is written to the MCP audit JSONL and emitted as an Activity Feed event.

Swarm dashboard

Swarm tasks tab, the default dashboard view.
Ctrl + Shift + S opens the nerve center for multi-agent work. You land on the Tasks tab.

The dashboard is where you watch a running swarm. Three tabs, each answering a different question about the active run.

Swarm messages tab.
Messages tab, live stream of every handoff and broadcast between agents.
Swarm trace tab.
Trace tab, the conductor's event timeline for the active run: plan → delegate → watch → merge.

What each tab is for

  • Tasks, answers "where are we?". Full DAG with Pending / In progress / Completed columns, per-task assignee and status, dependencies drawn as edges. Click a task to see its prompt, the agent's output so far, and the handoff chain.
  • Messages, answers "what are the agents saying?". Live stream of every handoff, broadcast, and tool result routed between agents. Use this to diagnose "why is Codex waiting?"
  • Trace, answers "what is the conductor thinking?". Timeline of the conductor's own decisions, planning, re-planning, agent picks, merge calls.

How to use it

  1. Open with Ctrl + Shift + S at any time, no swarm needs to be running.
  2. If no swarm is active, click Start Swarm. A folder picker opens firstchoose the project directory the swarm will work in, and only then does the Start Swarm wizard appear.
  3. Switch tabs freely, the dashboard stays scoped to the active run.
  4. Right-click any task → Open agent terminal to drop into that agent's live pty.
  5. Need to abort? Click the trash icon next to the swarm title to clear all messages and tasks (see below).
Clear Swarm confirmation dialog.
Clearing a swarm, the trash icon prompts for confirmation before wiping the current run's tasks and messages.

Clearing does not kill the underlying agent pty's. Use the intervention controls if you need to stop agents mid-flight too.

AI conductor

Start swarm wizard.
Start Swarm wizard — describe the task, pick your agents, set a budget. The working directory was already chosen in the folder picker that opened before this wizard, and is shown here for confirmation.

The conductor is a dedicated Claude Code instance running with a system prompt purpose-built for orchestration. When you launch a swarm, it's the conductor that reads your task, searches shared memory for relevant prior work, decomposes the task into subtasks, picks the best-fit agent per subtask (based on capability ratings + current load + cost), delegates via MCP, watches progress, and decides when to merge partial results, re-plan, or declare done.

Not keyword matching, actual AI reasoning, because the conductor is itself a frontier model. Open its terminal and watch it think live.

Before you start: a swarm always needs a project directory

Choosing a project directory is the first thing that happens, not a field inside the wizard. The moment you click Start Swarm, Termpolis opens your OS folder picker. The Start Swarm wizard will not open until you have picked a directory; if you cancel the picker, nothing launches and you are returned to where you were. This is deliberate, a swarm has no meaning without a root to work in.

That directory becomes the swarm's working directory, and it is inherited by everything the swarm creates:

  • The conductor is started in it, so its file reads, greps, and git commands resolve there.
  • Every agent terminal the conductor spawns is opened with that same cwd, so no agent can wander into an unrelated repo.
  • The directory is written into the conductor's task contract, so the instructions each agent receives name the path explicitly.
  • Shared memory and the code graph scope their recall to that project, so agents get context from this repo rather than everything you have ever worked on.
  • The pre-swarm git SHA is captured from it, which is what makes the post-run diff and the "revert this swarm" action possible.
Pick the repository root, not a subfolder. Agents routinely need to touch files outside the immediate area they are editing (tests, config, lockfiles), and a narrow root is the most common cause of a swarm that stalls saying it cannot find a file. Point it at a real git repo where possible — without git history there is no pre-swarm SHA, so the diff and revert affordances are unavailable.

Launching a swarm, step by step

  1. Press Ctrl + Shift + S or click Start Swarm on the Welcome screen or the Swarm Dashboard.
  2. Pick the project directory in the folder picker that opens immediately. This is required, the wizard does not appear until you choose one.
  3. Task, describe what you want built in plain English. Be specific; the conductor's decomposition quality scales with your clarity. The Launch button stays disabled until this is non-empty.
  4. Agents, toggle which agents are allowed to participate. Unchecked agents won't be assigned work.
  5. Budget, optional token and time ceilings. The conductor halts the run and asks you before exceeding.
  6. Launch, the wizard spawns the conductor in a hidden terminal and opens the Swarm Dashboard. The chosen directory is shown in the wizard so you can confirm it before committing.
One swarm at a time. While a swarm is active the Start Swarm button is replaced by a locked Swarm Active badge. Clear the current swarm before starting another — this keeps two conductors from issuing conflicting edits in the same repo.

What happens next

  1. Conductor reads your task → queries shared memory for related prior work.
  2. Decomposes into a DAG of subtasks with explicit dependencies.
  3. For each subtask, picks an agent: capability rating × inverse cost × current load.
  4. Spawns the agent terminal via create_terminal (MCP) and sends the prompt via write_to_terminal.
  5. Watches tool calls + token usage via the Activity Feed; intervenes if an agent stalls or errors out.
  6. When a task completes, writes the result to shared memory and unblocks dependents.
  7. When the DAG is done, presents a summary and offers a swarm review.

Not sure whether your task fits a swarm or a single agent? See the Swarm vs. single agent decision tree.

Activity feed

Activity feed.
Observability layer for every agent, every session.

Open from the sidebar (Ctrl + Shift + A) or any terminal's context menu. Event types: message, tool_call, tool_result, token_update, compaction, error, status_change, mcp_audit.

Three filters combine: full-text search, kind dropdown, agent-type dropdown. Open scoped to a terminal or globally across all sessions, scope is labeled in the header. Right-click an event → Pin to float it to the top of the context panel.

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.
  • Cancel, sends a single Ctrl+C (0x03).
  • Interrupt, sends double Ctrl+C (0x03 0x03), hard stop.
  • Steer, type a new instruction and send it directly to the agent's stdin.

Every agent is a pty, so writing control chars or text to stdin is the fastest, most reliable way to take over. No new IPC surface. Each intervention is logged as an event , full audit trail of every mid-flight correction.

Swarm review

When a task requires review before handoff (default for code-review tasks), the conductor pauses and opens the Swarm Review Panel, task title, assignee output, diff if applicable, and three buttons: Approve, Request Changes, Reject, plus an optional comment.

Approve hands off downstream. Request Changes reassigns to the same agent with your comment. Reject drops the output and re-plans.

Mneme — the shared memory that learns and never forgets

Mneme — named for the Greek muse of memory — is the local, growing “brain” shared by every agent that learns as you work. It indexes your past AI conversations (Claude Code, Codex, Gemini) and your repo’s code into a vector store on disk, distills a reusable lesson from each finished task, and lets any agent semantically recall what was decided or written weeks or even months ago — instead of you re-explaining context every session.

How Mneme works: every agent's work is captured, embedded locally into vectors, and stored in a vector database (find by meaning) plus a knowledge graph (find by connection) persisted as JSONL — then recalled and ranked, getting smarter over time.
How Mneme works: it files each memory by meaning (a local vector database) and by connection (a knowledge graph), both persisted as plain JSONL on your disk — then recalls and ranks them on demand.
Mneme's learning loop: reflect, consolidate, connect, rank, anticipate — repeating after every finished task so the shared memory keeps getting smarter.
The learning loop that runs after every finished task — reflect, consolidate (a “sleep” pass), connect, rank by what actually helped, and anticipate.
The Memory & Learning dashboard (Settings) proves it's working — what's stored by cognitive type, the live knowledge graph, learning over time, reliability SLIs, model portability, cross-agent teaching, and receipts. Every number is computed on your machine, offline, from the append-only store.
  • Learns from every session (since v1.17). When an agent finishes a chunk of work, Termpolis distills what happened into a reusable lesson — the problem, the fix or decision, the gotcha — and records the fleet’s self-competence per project, so recall keeps improving. Automatic for Claude, Codex, and Gemini (read from their session transcripts on a task pause or when the terminal closes). Opt-out in Settings.
  • Competence calibrated from real work (v1.25). The brain tracks how reliable it has actually been in each domain — the calibrated confidence that memory_selfcheck reports back. That calibration used to learn only from swarm tasks, or from a session that happened to end on an explicit outcome phrase, so for most people the panel simply sat empty. Now a landed commit and a test run — passing or failing — feed it directly. The failing suite is the valuable half: a red test run is what calibrates the fleet’s confidence down in a domain where it has been getting things wrong, instead of letting it carry on asserting.
  • Your code connections, on the dashboard (v1.25). Indexing a repo builds a structural code graph — symbols plus caller→callee edges — and that graph lives in a store separate from the semantic memory graph. The dashboard only ever counted the memory graph, so “connections” could read as empty on a machine that had already indexed thousands of call edges. The real code graph now surfaces as its own tile, so what you built is what you see.
  • Shared across all three agents. Claude, Codex, and Gemini all read and write the same store over MCP, so a fact one agent learns is instantly available to the others.
  • Flags cross-agent contradictions (new in v1.19.5). When two agents record lessons that assert opposite things about the same subject — a Claude lesson says “always run migrations before seeding,” a Codex lesson says “never” — the memory_conflicts MCP tool surfaces the pair so you can investigate and resolve it (record the winner, mark the loser with a supersedes link). Read-only and deliberately high-precision — it would rather miss a subtle conflict than raise a false one.
  • Survives close & reopen. Stored as JSONL on disk (swarm-memory.jsonl in the data directory) and reloaded with its embeddings at startup, your context isn’t lost when you quit.
  • Feeds itself. A background indexer ingests new sessions on a quiet timer (idempotent, content-hash deduped), so the brain grows with no action from you.
  • Never stores the same thing twice. Every write is content-addressed (SHA-256 over normalized text), so identical information is a no-op — the vector store and the on-disk log never accumulate duplicates, and nothing the brain already holds is re-embedded.
  • Fully offline, no server. Embeddings run in-process via WASM with a bundled bge-small-en-v1.5 model, no Ollama, no native binaries, nothing leaves your machine. And since v1.19.4 the embedding runs on a background worker thread — not the one echoing your keystrokes — so typing stays smooth while the brain loads its model and indexes.
  • Secrets are never indexed. The code indexer reuses the same sensitive-file denylist as the read watcher, so .env files, keys, and cloud credentials are excluded.
  • Syncs across machines. Point the memory at any synced folder (Dropbox, OneDrive, iCloud Drive, Google Drive in mirror mode, or Syncthing) and every device shares one brain. Each machine writes its own shard (<device-id>.jsonl), so the merge is conflict-free — a grow-only union with tombstones, no central server and no Termpolis account.
  • Portable — export / import the whole brain (v1.21). Export Memory (app menu) writes a termpolis-brain-<date>.zip holding your memories, the knowledge graph, per-domain competence, identity, metrics, and the code graph — each SHA-256’d in a manifest. Import Memory verifies the zip CRCs and every hash before applying anything (a corrupt or tampered archive is refused whole, never half-merged), then grow-only-merges: memories and edges union in; the device-local learning files restore only on a fresh machine, never overwriting an existing brain. Your per-machine identity, sync config, and encryption salt are deliberately excluded. Back up, seed a new machine, or move your brain over any transport you like.
  • Encrypted at rest by default. A local store is sealed with AES-256-GCM under a per-device key generated into your OS keychain (Windows DPAPI, macOS Keychain, Linux libsecret) — no passphrase required, and where no keychain is available it stays plaintext and reports so honestly rather than writing a key beside the data. With cross-machine sync the key is instead derived with scrypt from a passphrase you share, never stored in the synced folder, so your cloud provider only ever sees ciphertext. Termpolis transparently decrypts on read.
  • Scales into six figures. Vectors are packed into a typed-array store (about half the RAM of boxed arrays); past tens of thousands of entries an HNSW approximate-nearest-neighbour index engages automatically so search stays sub-linear (a few ms/query, measured). The graph lives off the JS heap and persists to disk. It builds once, lazily, in the background without blocking your searches — the first query after crossing the threshold returns instantly from the exact fallback while the index builds (frame-budgeted so the UI never stalls); every later search and launch uses the saved graph.
  • It learns the connections. Beyond storing facts, the brain builds a knowledge graph — typed links between memories (bug → solved-by → fix, decision → supersedes → …). Agents record links explicitly with memory_link and follow the chain across hops with memory_graph; curated writes auto-link to their nearest neighbours, so the graph gets denser — and recall gets smarter — the more you use it.
  • Consolidates while idle — the “sleep” pass (v1.17). Between tasks a planner compresses the brain the way sleep consolidates memory: stale, untagged, edge-free episodic chatter decays out, near-duplicates merge, and dense clusters roll up into a summary. It only ever plans compression and writes archival edges — the “forgettable” gate is deliberately narrow, so a tagged, linked, or important memory is never destroyed.
  • Reasons over the edges, not just stores them (v1.17). Relation types matter at recall time: a solves or caused-by edge pulls its neighbour up, a memory a newer one supersedes is dropped so stale answers stop resurfacing, and an edge outside its valid-from / valid-to window is excluded from traversal.
  • Ranks by learned utility, not raw similarity (v1.17). A hit’s score fuses semantic relevance, recency, per-kind weight, importance (reflection scores a distilled lesson high), and how often that memory has actually proven useful (reinforced via memory_feedback) — then an MMR diversity rerank keeps a cluster of near-identical hits from crowding out varied context, with a floor so a thin recall never starves the agent.
  • Auto-recovers from compaction. When Claude Code compacts its conversation to fit the context window, Termpolis watches the terminal, waits for the compaction to settle, and re-injects the most relevant memories into the agent’s input (ready to send, never auto-submitted) so it picks right back up. Debounced through the whole compaction and cooldown-guarded to fire once; opt-out in Settings.
  • See compaction coming. A live context-pressure pill in the status bar shows how full the focused agent’s window is — healthy → filling up → nearly full → compaction imminent — from real token counts when the agent reports them (Claude) or a labeled heuristic otherwise. You watch the pressure build and know recovery is handled, instead of being surprised by a compaction.
  • Observable, fresh & trustworthy recall (v1.16.7). Memory priming is invisible by design (a system-prompt file plus a SessionStart hook), so a working recall used to look identical to nothing happening. Now every primed launch shows a banner — 🧠 Loaded N memories, or a clear warning if the brain was unreachable. A fast indexer tier (~90 s cadence, freshness-limited) makes the active session searchable within seconds instead of waiting the full 30-minute pass; recall flags any code reference whose file has since been deleted as ⚠ STALE rather than handing it back as fact; the memory hook resolves an absolute Node path so it still fires under thin-PATH login shells (NVM and friends); and an adaptive relevance floor keeps weak, off-topic hits out of the digest entirely.

The MCP tools any agent can call:

  • Search via memory_search, semantic vector search blended with keyword overlap.
  • Write via memory_write, persist a fact or decision for the others.
  • List via memory_list, recent entries with filters.
  • Relate via memory_related, jump from one memory to its neighbours — undirected since v1.23, blending typed edges with nearest neighbours.
  • Link & walk via memory_link and memory_graph, record typed connections and follow the chain across hops.
  • Anticipate via memory_anticipate, surface solutions the fleet already found for the task at hand before you start solving it.
  • Pool via memory_pool, gather the cross-agent lessons that two or more agents independently arrived at — the fleet’s most-corroborated knowledge.
  • Self-check via memory_selfcheck, ask the brain how reliable it has been in a domain (calibrated confidence from past outcomes).
  • Reinforce via memory_feedback, mark a recalled memory as helpful so the ones that keep paying off rank a little higher for everyone.

Why it matters: when Claude figures out your auth module, Codex shouldn’t have to figure it out again, and you shouldn’t burn tokens re-pasting context every session. A pre-context primer can inject the most relevant memories at an agent’s first prompt so it starts already knowing the background. And because the brain lives outside any model’s context window, it holds months of history without forgetting: when an agent’s own window compacts, the detail isn’t lost — it’s still in the store, one memory_search away. (Termpolis doesn’t change how a model compacts its own window; it makes compaction non-lossy and saves you from reloading context.)

Controls: open the Memory panel (Ctrl+Shift+M, or the Command Palette → “Memory”) to see what’s remembered, search it, feed it on demand (“Index past conversations” / “Index this repo’s code”), and inject the most relevant context into the active agent with one click.

Vector memory & the int8 toggle (v1.25.5)

Settings → Memory & Learning → “Vector memory”. Storing your embeddings as int8 instead of exact float32 uses 4× less vector RAM (1 byte per component instead of 4). That is the entire mechanism. The interesting part is that Termpolis will not simply hand you the switch.

Why this needs a panel at all. Your vectors live in the main process — the same thread that pumps the PTY. So vector RAM is not an abstraction: at multi-gigabyte scale it means garbage-collection pauses on the one thread whose stalls you feel as typing lag. (We have been here before: an in-process WASM embedder pinning the main thread was a real, shipped lag bug.) Nobody can answer “should I enable int8 vector quantization?” in the abstract, and a bare toggle would be a trap — it sounds like an optimization, so people would flip it on principle. It is therefore a decision aid: it measures your machine, live, and tells you what to do.

What it measures (polled live, every 2 s)

ReadingWhat it tells you
VectorsHow many, at what dimension, and in which representation — float32 (4 B/component, exact) or int8 (1 B/component).
Vector RAMWhat they actually hold right now, and what they would hold in the other representation. (They live in arrayBuffers, off the V8 heap — so a heap graph would not have shown you this.)
Process RAMThe resident set, and the vectors’ share of it. This is the number that decides whether the toggle is even relevant.
Main-thread stall (p99)Event-loop delay — the actual symptom. Above 50 ms is roughly three dropped frames: the point where keystroke echo stops feeling instant.
Longest GC pauseThe worst stop-the-world pause, plus how many major collections have run. A >50 ms pause is a visible hitch.
Time spent in GCGC as a share of wall-clock over the sampling window. Above a few percent, the thread is fighting the collector.

The verdict — including the one that says don’t

From those numbers the panel gives a recommendation, and the burden of proof sits on enabling:

VerdictWhenWhat it says
Not neededVectors under ~256 MBThere is no version of this that helps — stalling or not. Leave it off. This is the correct state for most users, forever, and the panel will tell you if that changes.
Won’t helpThread is stalling, but the vectors are under ~20% of the process“Your main thread is stalling — but not because of the vectors.” Freeing them would not fix the stalls, and you would lose exactness for nothing. Look elsewhere.
Your callVectors are large, nothing is degradingThe thread is healthy, so there is no problem to fix — but the memory is real, so taking the headroom back is defensible.
RecommendedThread is stalling and the vectors are a large share of itWorth turning on. It names how much it expects to free.
int8 onAlready enabledExact floats are still on disk; switch back at any time and lose nothing.
A control that only ever markets itself is an upsell, not a tool. Won’t help is the verdict that makes the other four worth reading. The cost of a wrong yes here is invisible (you quietly approximate the one thing the brain exists to do) while the benefit is imperceptible until the corpus is genuinely large — so the panel is built to be willing to talk you out of it.

Does it cost recall?

No measurable loss — and that is gated, not asserted. Retrieval is two-stage: a fast int8 gather over a widened candidate set, then a rescore of those candidates with the exact float query against the dequantized rows, which recovers the precision that matters for the final ranking. The offline recall benchmark runs the real bundled bge-small-en-v1.5 model against the float baseline and fails the build if int8 regresses it: measured recall@10 and recall@5 identical to the exact-float baseline, with nDCG and MRR within 0.001. It is only worth shipping if it keeps recall, so that is a CI gate rather than a promise.

Turning it on and off

  • Off by default. Exact vectors are the better default until memory is actually costing you something.
  • Losslessly reversible. The copy on disk always keeps exact floats. int8 is an in-RAM representation — it is not a data migration, and nothing is ever destroyed. Turning it on repacks the in-memory store; turning it off restores exact floats from disk on the next load. Memories written before the flip are still there, and still recallable, after it.
  • It only touches the vectors. Your memories, the knowledge graph, the code graph, and the audit trail are unaffected.

The Weave — one fabric across memory, code, and every repo (v1.23)

v1.23 “The Weave” joins the shared memory and the code graph into a single self-weaving fabric. Recall stops at text — it points at the code a lesson is about — and the non-obvious connections across your whole workspace are drawn ahead of time, so agents reason faster. Everything below is local-first, append-only, and native-free, and forms as you work — nothing to set up.

  • Memory ↔ code bridge. Stored lessons and decisions now carry structured code anchors (file + symbol), resolved from the code graph when the lesson is written. Recall can cross straight from a fix to the exact function it lives in, and a reverse lookup surfaces everything the brain knows about a given symbol. The two stores used to have disjoint id spaces; now they share a join key.
  • Predict where to fix it — code_locate. Give it an error or a problem description and it returns a ranked list of {file, symbol, why:[past lessons]} — the code sites most likely responsible, each with the fixes and decisions anchored there, scored by code-graph centrality × the utility of those lessons. It’s the 32nd MCP tool, so an agent reaches for it first when debugging instead of grepping. The why list grows richer over time as new lessons are anchored and the weaver backfills older ones.
  • The Weave — an always-on background connection-miner (flagship). Between tasks, a weaver continuously draws connections across the entire unified brain: cross-repo code-structure analogies (a pattern in one repo that echoes one in another), cross-repo answer/decision analogies, and the memory↔code bridge edges above — each materialized ahead of time with provenance and a confidence/weight floor so the graph stays high-signal rather than flooded. It runs on the same idle indexer tick that powers semantic recall.
  • Automatic bug → fix edges. When a task that hit a problem finishes, Mneme now mints the causal solves edge to an entity node for the error automatically, so “this error → that fix” is traversable later without anyone hand-linking it.
  • Rock-solid, never-delete memory. Idle consolidation moves aged memories to a cold-archive tier instead of deleting them — nothing curated is ever permanently lost — and deep recall can still reach archived entries and history beyond the hot search window. Cross-repo transfer is relevance-scoped, so one unified brain gives cross-project reuse without the noise.
  • Sharper learning. An opt-in LLM distiller (set TERMPOLIS_MNEME_DISTILLER=1) writes richer, more precise lessons than the default heuristic; memory_related is now undirected, so a typed connection surfaces from either end.
  • The explains edge — memory bound to the code it explains (v1.25). A new typed edge links the lesson that explains a piece of code to the code chunk itself. It is deliberately hard to earn: an edge is minted only when the memory and the chunk clear both an embedding-similarity bar and a shared file/symbol anchor — semantic resemblance on its own is not enough, so the graph doesn’t silt up with vague thematic links. The weaver also mints intra-repo analogies now, not just cross-repo ones (a pattern that echoes another pattern in the same codebase was previously invisible to it), and the analogy similarity floor relaxes from 0.82 to 0.72 — enough to surface the useful near-misses the stricter floor had been quietly dropping.

Code graph — structural code understanding (automatic)

Alongside the semantic memory, Termpolis builds a native code graph of your repository — every function, class, method, and the calls between them — so your AI agents can answer “who calls this?”, “what would this change break?”, and “where is X defined?” in one step instead of grepping the same files over and over.

It’s automatic. Opening a terminal in a repo indexes that repo’s code, and an edit re-indexes just the changed file (debounced, AST-first) — the same setting (Auto-index everything, on by default) that powers semantic recall. No clicks; opt out in Settings. As of v1.23 the graph is keyed per repository — a durable on-disk store per repo, so opening a second repo no longer clobbers the first, and a transient non-git directory (or git off the PATH) won’t wipe a graph you’ve already built.

How agents use it. The graph is exposed to every launched agent (Claude Code, Codex, Gemini CLI) over MCP as six read-only tools, and Termpolis nudges them to prefer these over grepping:

  • code_explore — a symbol’s source plus its direct callers and callees, in one call.
  • code_callers / code_callees — who uses this / what it depends on.
  • code_impact — the transitive blast radius of a change, before you make it.
  • code_search — locate any symbol by name across the codebase.
  • code_locate — predict where an issue lives (file + symbol + the past lessons that point there), crossing the memory↔code bridge. New in v1.23.

Browse it yourself. The Memory panel (Ctrl+Shift+M) includes a Code Graph browser — search a symbol to see its source, callers, callees, and blast radius, or force a rebuild.

Languages. Deep, AST-precise support (symbols + methods + call edges) for TypeScript/JavaScript, Python, Go, Rust, Java, C#, Ruby, and Swift — parsed by web-tree-sitter (WebAssembly) with pre-built grammars; Terraform and Bicep fall back to a regex heuristic for symbol discovery. Still fully local and native-free — the parser is WASM, not a native binary, so nothing is compiled and nothing leaves your machine; secrets (.env, keys) are never indexed.

Observability

A full observability stack for AI work, the "watchers" system. A lightweight in-process event bus that watches for:

  • Stuck sessions, agent silent for > N seconds.
  • Error cascades, repeated errors in a short window.
  • Redundancy, two agents doing overlapping work.
  • Efficiency, rolling token-cost-per-task average.

Watchers surface alerts in the status bar, activity feed, or system notifications. Thresholds tunable in settings.

Status bar

Status bar at the bottom of the window.
Active workspace, git branch, agents, swarm status, tokens, MCP health.

Bottom strip, left to right: workspace + git branch, focused terminal's shell + cwd, active-agents summary, swarm progress %, session-wide token counter, watcher notifications, MCP server indicator (green = healthy).

Keyboard shortcuts

ActionWindows / LinuxmacOS
Command paletteCtrl+K+K
New terminalCtrl+Shift+T++T
New terminal (global — works when minimized)Win+Shift+T++T
Close focused terminalCtrl+Shift+W++W
Next / previous terminalCtrl+Tab / Ctrl+Shift+Tab+Tab / ++Tab
Jump to terminal NAlt+1…9+1…9
Launch agent 1–3 (Claude / Codex / Gemini)Ctrl+1…3+1…3
Toggle sidebarCtrl+B+B
Toggle split / grid viewCtrl+Shift+G++G
Prompt templatesCtrl+Shift+P++P
Context panelCtrl+Shift+E++E
History searchCtrl+Shift+H++H
Conversation searchCtrl+Shift+I++I
Activity feedCtrl+Shift+A++A
Context pinsCtrl+Shift+B++B
Redundancy panelCtrl+Shift+D++D
Efficiency panelCtrl+Shift+Y++Y
Swarm dashboardCtrl+Shift+S++S
Memory panel (copies the selection as a code block when a terminal is focused)Ctrl+Shift+M++M
Voice dictation (push-to-talk)Ctrl+Shift+L++L
Copy / paste (in terminal)Ctrl+Shift+C / Ctrl+Shift+V+C / +V
Keyboard select / copy modeCtrl+Shift+Space++Space
Trigger autocompleteCtrl+Space+Space
Keyboard-shortcuts list (Settings → Keybindings)Ctrl+/+/

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, 34 tools)                    │
│  ├── 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 30, React 18, TypeScript 5, Vite 5 (electron-vite), node-pty, xterm.js, Vitest (7,000+ tests across 344 files, 98%+ line / 94%+ branch coverage), Playwright, electron-builder, SSL.com code signing, notarytool.

Troubleshooting

Found a bug that isn't here? Open an issue on GitHub → Include your OS + version, your Termpolis version (shown in the bottom status-bar footer, or under Help / Support), and any relevant audit logs from your data directory (e.g. mcp-audit.log).

Installation & first-run

Windows: "Windows protected your PC" SmartScreen warning. Click More infoRun 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." 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.

Linux: .deb fails with “Permission denied / pkgAcquireRun: 13”. apt’s sandboxed _apt user can’t read files in your home directory on Ubuntu 22.04+. Install with dpkg instead, which doesn’t drop privileges: sudo dpkg -i ./termpolis_*.deb. v1.11.30+ ships a postinst that auto-runs apt-get install -f -y to resolve any missing dependencies, so a single dpkg -i is now enough.

Linux: window opens as a blank black box. Common on Ubuntu setups with NVIDIA proprietary drivers or some Wayland compositors — Chromium’s GPU process initializes but fails to compose output. v1.11.30+ ships --no-sandbox --disable-gpu baked into the .desktop launcher so the dock icon Just Works on these systems. If you launch the binary directly from a shell on an older build, pass the same flags: /opt/Termpolis/termpolis --no-sandbox --disable-gpu. TERMPOLIS_DISABLE_GPU=1 termpolis remains as an env-var escape hatch.

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

Terminals

Terminal won't start. Check the shell path in Settings → Shells. On Windows, PowerShell 7 lives at C:\Program Files\PowerShell\7\pwsh.exe; WSL needs wsl.exe on PATH. On macOS, if /bin/zsh gives "permission denied", re-grant Termpolis Full Disk Access, 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. The shell detected a non-TTY environment. Make sure the Agent profile field is empty if you're launching a plain shell. Settings → Shells → Reset defaults fixes most cases.

Copy/paste shortcuts don't work. On Windows/Linux, use Ctrl + Shift + C / V inside terminals (bare Ctrl + C sends SIGINT). On macOS, + C / V work everywhere.

Font looks wrong / icons are boxes. The app ships its own icon font, but if it failed to load (usually due to a theme override), re-select a built-in theme or run Reset theme from Settings → Themes.

Agents & CLI tools

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

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. Override per-agent in Settings → Agents.

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

Swarm, MCP, and memory

MCP indicator in status bar is red. The MCP server failed to start. Common causes:

  • Port 9315 already in use, another instance or a crashed process still owns it. Termpolis automatically tries the next free port (9315–9319) and writes the one it bound to mcp-port in the data directory; the status bar shows the active port. Kill stray termpolis processes if none can bind.
  • Firewall blocking localhost, rare but possible. Add an exception for the Termpolis binary.
  • Token file write failed, mcp-token couldn’t be written to the data directory. Make sure your data directory is writable.

Start Swarm does nothing / the wizard never opens. Start Swarm opens an OS folder picker before the wizard, and the wizard only appears once a directory is chosen. If you dismissed the picker (Esc or Cancel) nothing launches, that is the expected behaviour. Click Start Swarm again and pick a project directory. If the button reads Swarm Active instead, a swarm is already running, clear it first.

Agents can't find files / edit outside their task. Almost always a too-narrow project directory. Every agent terminal inherits the swarm's working directory, so a swarm rooted at src/components cannot reach the tests, lockfile, or config that live above it. Clear the swarm and relaunch from the repository root.

Swarm conductor doesn't launch. The conductor spawns a Claude Code child process that needs claude on PATH. Open the conductor’s terminal (Swarm Dashboard → right-click the task → Open agent terminal), or use Conversation search (Ctrl + Shift + I), to see its startup output.

Swarm hangs mid-task / agents stop posting activity. Open Activity Feed, if the agent is running but not emitting events, its MCP connection may have dropped. Use Pause → Reset session in the Swarm Dashboard to recover. If a specific agent repeatedly drops, its MCP token probably expired, restart Termpolis for fresh tokens.

Memory search returns nothing. Embeddings run in-process via WASM (bundled bge-small-en-v1.5) — no Ollama required. If recall is empty, the brain may simply be unindexed: open the Memory panel (Ctrl+Shift+M) and run Index past conversations / Index this repo’s code, or wait for the background indexer (first pass ~10 s after launch). The first embed lazily unpacks the WASM model, so the very first search after a cold start can take a few seconds.

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. On Linux, download and replace the AppImage.

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

Terminal scrollback is sluggish. Default xterm scrollback is 10,000 lines. If you've pasted very large logs, scrolling slows down. Settings → Terminals → Clear scrollback resets without restarting.

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. Workspaces will be empty but the app is usable again; the old file is preserved for diffing later.

Reset everything. Close Termpolis, delete the entire data directory (see Installation), relaunch. Wipes workspaces, settings, themes, prompt templates, custom workflows, swarm history, and memory.

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 in the bottom status-bar footer, or under Help / Support).
  3. Steps to reproduce, as minimal as you can make them.
  4. Relevant audit logs from your data directory (mcp-audit.log, ai-security-audit.jsonl) and, for swarm/agent issues, the agent’s terminal output or a Conversation search result.
  5. A screenshot or short screen recording if it's a UI bug.