neuron-explorer
Qwen3.5‑4B 전용 조향형 뉴런 탐색기, 모든 MLP 뉴런을 밀어 결과를 보관·탐색
Claude·Codex·Gemini용 로컬 멀티에이전트 터미널, 작업하면서 학습하는 공유 메모리와 교차 검토, 모델 경로 보안 제공
The local-first multi-agent terminal for Claude, Codex, and Gemini — one shared memory that learns as you work, Second Opinion cross-agent review, and a security perimeter around the hosted-model path
개발 도구TypeScript · JavaScriptGitHub @codedev-david온라인운영자 미확인
써 보셨어요? 한 번만 눌러 주세요. 로그인은 필요 없어요.
AI terminal tool supporting multiple AI Terminals, multiple agents and an internal memory that learns over time and never forgets.
저장소 README에서 가져옴
README 전체를 불러오는 중입니다. 아래는 저장된 발췌입니다.
Stop re-explaining your codebase to AI.
Claude, Codex, and Gemini share one local memory that learns as you work —
so every agent already knows your project, your decisions, and what got figured out yesterday.
100% on your machine. No cloud. No telemetry.
🙏 Free & open source. Found a bug? Open an issue — we read every one.
🧠 Meet Mneme — one memory, every agent, that learns as you workREADME 전문 보기 ↗Every AI session normally starts cold — you re-explain the task and burn 20–50K tokens reloading context. Mneme (named for the Greek muse of memory) is the local brain at the heart of Termpolis: one shared memory that Claude, Codex and Gemini all read and write, that remembers across sessions and learns from each one, so you stop repeating yourself. (Since v1.51, Gemini through the Antigravity CLI, agy, which Termpolis now connects to its MCP server.)
bge-small-en-v1.5, WASM) — no server, no telemetry; the memory never leaves your machine. The local store is encrypted at rest (AES-256-GCM) by default whenever the OS keychain is available (DPAPI on Windows, Keychain on macOS, libsecret/KWallet on Linux): each device gets a random key, which is itself stored encrypted by the OS. With no keychain (for example a Linux box without a keyring) the memory stays plaintext rather than sitting next to an unprotected key. Aged memories that idle consolidation moves to the cold archive (swarm-memory.archive.jsonl) are written under the same key; entries an earlier version archived stay plain text for now, as do the link graph and the corrections log (which holds the text of any amended memory). The key file is never overwritten or deleted: if this computer can't open it (no keychain, a password reset), memory comes up locked — nothing is lost and no new memory is saved — and the Memory panel says why, and offers to start a new key while keeping the old one (a new key needs a working OS keychain). Each computer keeps its own memory.🧠 Loaded N memories banner so you always know it fired.Proven, not just claimed. A CI gate has one agent write a decision and a different agent recall it from a keyword-free paraphrase over the real MCP wire; semantic recall scores 0.97+ similarity on paraphrases. Backed by 7,000+ tests (97.5% statements / 98.5% lines).
The result: stop re-explaining context every session — and stop paying to reload it.
Termpolis runs those exact CLIs, unchanged — and fixes the one thing they can't do alone: remember.
| What you get | Claude Code / Codex on their own | The same agent inside Termpolis |
|---|---|---|
| Long-term memory | Starts cold every launch; you re-explain and re-pay tokens to reload context | Mneme — a local brain holding months of context, surviving restarts, recalled in ms |
| Shared across tools | Siloed — Codex can't see what Claude just figured out | Claude, Codex and Gemini read/write one memory; a fact one learns, the others recall |
| It learns | No learning — every session is a blank slate | Distills a lesson from every finished task + tracks its own competence |
| Every new session | Re-explain your codebase from scratch | Opens already knowing the project; one-click cross-agent handoff, no re-explaining |
| Your data & lock-in | One vendor's account + cloud; telemetry varies | Local by default, Apache-2.0, no account/backend; prompts to Claude Code, Codex and Gemini scanned for secrets |
Same agents, same accounts you already pay for — Termpolis is the workspace around them that remembers and learns.
Honest framing first. Any tool that lets you talk to a hosted model (Claude, Codex, Gemini) is, by definition, sending your prompt to that provider. Termpolis cannot air-gap a prompt you choose to send, cannot guarantee a provider's stated retention policy is enforced server-side, and cannot stop a provider from later changing their terms. If your threat model requires those guarantees, run a local model — but accept the quality + hardware trade-off that comes with it.
What Termpolis can do is make the hosted path substantially safer than typing into a stock terminal, a browser, or a VS Code plug-in:
| Risk | What Termpolis does | Limit |
|---|---|---|
| Secret leaves the machine in a prompt | Always-on prompt watch (v1.25.2) — no toggle, cannot be switched off. Every Enter / paste in an AI terminal is scanned against 97 rules: token shapes (AWS, GitHub PATs, Stripe, GCP, JWTs, PEM keys), named assignments in .env / appsettings.json / YAML / connection strings / URLs-with-credentials, and a contextual rule that catches the most human leak of all — "here is the api key for this code, add it to line 42: 8f3a9b2c…". Your text is forwarded untouched — nothing is withheld, nothing is rewritten. A hit is recorded, naming what leaked (DB_PASSWORD) so you know what to rotate. | It records; it does not prevent — and we no longer pretend otherwise. By the time you press Enter the agent's TUI already holds the text; nothing can un-send it. (The old "redaction" toggle claimed it could. It couldn't, and it was broken: it withheld keystrokes and never wrote them back, so typing hello⏎ delivered only \r. It is deleted.) Prevention lives at the two boundaries where it is genuinely possible: the git shield and the memory scrub. The secret's value is never written to the log — names and rule ids only. A shapeless secret pasted with no surrounding words remains undetectable. |
| Secret gets committed, then pushed to a remote | Commit/Push Secret Shield (v1.25) — the same 97-rule engine now also runs at the git boundary. Commit through Termpolis (the built-in Git panel, or Swarm Review's commit) and it scans the staged diff — exactly what git commit is about to capture. Push through Termpolis and it scans the full patch of every commit not yet on any remote — exactly what git push is about to send. A hit blocks the operation and names the rule that fired. The outbound scanner only ever saw text typed at an agent; it structurally never saw git, so a leaked key could still land in history and reach a remote. On by default. | Out of the box it gates the git operations you run through Termpolis. To cover a git commit typed straight into a terminal — or run from an IDE, a script, anything — install the pre-commit / pre-push hooks: . They shell out to a carrying its own copy of the rule table, so they keep working (a hook that only guards you while the app is running would silently stop guarding you the moment you quit — worse than no hook, because you'd still believe you had one). An existing hook (husky, lint-staged) is , and its exit code still gates the commit. The hook : if Node or the scanner is missing, git is never blocked. bypasses any git hook — this is a strong net, not a cage. Regex-shaped secrets only, as above. |
v1.25 moved the perimeter to the boundaries that actually leak. The secret engine used to watch exactly one of them — the keystrokes you send to an agent. It now also gates what a commit made through Termpolis captures, what a push made through Termpolis sends, and what gets written into the shared brain; an imported skill or MCP server is scanned before it is wired into an agent; and agent egress is judged against an allowlist rather than merely logged. Those gates — and the audit log — default to on, and an absent setting key keeps the secure default, so an existing install is protected on upgrade without touching Settings. Each can be turned off individually in Settings → AI Security; Safe Import lives in Settings → General.
v1.25.6 — three controls that were quietly not firing. Every one was found by writing a test against code nobody had tested. If you are on 1.25.5 or earlier, this is what was silently not protecting you:
secring.gpg — your private keyring — had been grouped into the sensitive-file rule's own exclusion list beside the public keyrings, so the exclusion returned before the match could. A read of your private keyring was never flagged, and the failure mode was total silence, which reads exactly like "nothing happened". Only the pubring.* entries are excluded now.NaN limit defeated the audit-log clamp and returned the entire log. typeof NaN === 'number' is true, so a NaN limit took the clamp arm rather than the 200 default — and Math.min/Math.max propagate NaN rather than clamping it, so the read degraded to "return everything", which is the one thing the 2,000-entry cap exists to prevent. The guard is Number.isFinite now.!==, and install stores either the renderer's cwd (forward slashes) or the native picker's OS-native path (backslashes) — so install-by-picker followed by uninstall-by-cwd never matched, and the repo stayed on the list with its hooks already gone. A security control that claims to be armed when it is not is worse than one that admits it is off. It is keyed on a canonical path now (Windows-only separator/case folding — a backslash is a legal filename character on POSIX, and folding it there would conflate two genuinely different repos).Also fixed: the Strict-Mode Gemini refusal message never rendered on Windows. The banner was written to the PTY as a typed printf command, which only works on a shell that has printf — so on cmd.exe / PowerShell you got 'printf' is not recognized instead of the explanation, at the exact moment you most needed to know why the launch was refused. The block always worked; the message was what failed. It now goes straight to the renderer.
What this is and isn't: Termpolis is defense in depth for the hosted-model path — it raises the cost of accidental disclosure and gives you a record to audit. It is not a guarantee that source code cannot reach a provider — only not running the agent at all gives you that. The honest answer to "can a hosted model leak my code?" is "yes, if you send it; the question is whether the controls catch the obvious accidents and whether you trust the provider's terms for the rest." Termpolis is built for the engineers who've decided that trade-off is acceptable for the productivity hosted models give them.
See PRIVACY.md for the data-flow spec, TERMS.md for the Apache-2.0 / "AS IS" disclaimer.
The moat above in full — every capability of the brain that all three agents share:
memory_search / memory_write / memory_list / memory_primer). Claude and Codex sessions are learned from automatically, without the <project-memory> block a headless run opens with (that is recalled memory, which would otherwise be stored twice; a run an earlier version already stored keeps the form it was stored in, so it isn't stored a second time); Gemini (agy) keeps its sessions where Termpolis can't read them, so it is asked at launch to record its own lessons with memory_write.swarm-memory.jsonl, one entry per line, each line encrypted when the OS keychain is available) in Termpolis's per-user app-data folder — %APPDATA%\termpolis\ on Windows, ~/Library/Application Support/termpolis/ on macOS, ~/.config/termpolis/ on Linux — and reloaded with its embeddings at startup. Because it lives in your user profile (not the install folder), it survives app updates and even an uninstall/reinstall — close Termpolis, reopen it tomorrow, the context is still there.bge-small-en-v1.5 model — no Ollama, no native binaries, nothing leaves your machine. The indexer reuses the same sensitive-file denylist as the read watcher, so .env files, keys, and cloud credentials are never embedded.How it works:
onnxruntime-web (WASM).memory_search over MCP and gets the most relevant past context back, blending semantic vector search with keyword matching.The result: stop re-explaining context every session, and stop paying to reload it.
Controls — open the Memory panel (Ctrl+Shift+M, or the Command Palette → "Memory") to see what's remembered (chunk count), 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. Launched agents are also auto-primed with the project's relevant context (toggle in Settings): a one-line note in the agent's input points it at the memory_primer MCP tool, so the digest loads behind the scenes — no giant dump on the terminal — and the agent holds it as background instead of acting on it or resuming old work uninvited. Context from the current directory's project comes first (its past conversations, then its code/notes), and anything recalled from other projects is clearly labeled as possibly not applying.
The digest reserves slots for what happened most recently (v1.33.0). Relevance ranking answers "what is most similar to this query"; a session-start digest also has to answer "where did we leave off", and only a time-ordered read can. Recency used to be a nudge inside the score — with a 30-day half-life and a 0.25 weight, an hour-old memory outranked a 22-day-old one by under 9%, which semantic similarity routinely swamped. The result was a primer for an active repo that carried five-day and three-week-old items and skipped that same morning's work entirely. Now up to 3 of the digest's slots are filled newest-first, scoped to the same repo, deduped against whatever relevance already picked — spent from the same budget, so the digest gets fresher, not bigger. The ranking weights themselves are untouched, so global recall is unchanged.
v1.23 "The Weave" turns the shared brain and the code graph into a single, self-weaving fabric. Recall no longer 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.
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 that point there. It's exposed as an MCP tool, so any agent can reach for it first when debugging instead of grepping blindly. (The bridge fills in over time: why gets richer as new lessons are anchored and the background weaver backfills older ones.)solves edge automatically, so "this error → that fix" is traversable later without anyone hand-linking it.TERMPOLIS_MNEME_DISTILLER=1) writes richer, more precise lessons; memory_related is now undirected, so a connection surfaces from either end.Termpolis doesn't replace Claude Code, Codex, or Gemini CLI — it runs them, unchanged, and adds the layer they're all missing. A bare AI CLI (or an IDE assistant like Cursor or Copilot) is a single vendor's model talking to a single session: no memory of yesterday, no awareness of the other tools you use, and no guardrail on what leaves your machine. Termpolis turns that into a coordinated, persistent, auditable workspace.
| What you actually get | A bare AI CLI / IDE assistant | The same agent inside Termpolis |
|---|---|---|
| Memory across sessions | A per-session context window that resets cold every launch — you re-explain the task and re-pay the tokens to reload it | One local brain all three agents share, surviving restarts; auto-fed from past transcripts; observable + staleness-guarded (v1.16.7) |
| More than one model | Locked to one vendor's family | Claude + Codex + Gemini run as a team under a local conductor that routes each subtask to the best-suited model |
| Secrets / code leaving the machine | Whatever you type is sent as-is, and you never find out | Every prompt is scanned against 97 secret patterns. Your text is forwarded untouched, and a hit is logged by name (DB_PASSWORD) so you know what to rotate — the value is never stored. And since v1.25 the same engine actually blocks a git commit or git push that would carry a secret into history or to a remote |
| Importing a third-party skill / plugin / MCP server | Wired straight into the agent's config — a zip nobody diffs, whose instruction text the agent reads as if you'd typed it | Safe Import statically scans it first (41 rules, including prompt injection hidden in the artifact's own prose). Red is never installable, and approvals are hash-pinned so an edited artifact re-prompts |
| Knowing what the agent contacted | Opaque — you can't tell who it talked to | Egress Guard judges any endpoint it is given against a provider allowlist. The per-agent egress audit (netstat / ss / lsof) that fed it is off: Termpolis lists no network connections, which endpoint security would read as discovery |
| Telemetry on you | Often product analytics or cloud-stored chat history | None by default — memory, history, and the audit log stay in local JSONL on your disk |
| Lock-in | Vendor account, cloud backend, or a specific IDE | Apache-2.0, local-first, no Termpolis account, no Termpolis server |
The short version: other harnesses optimize a single agent's loop. Termpolis optimizes your whole agent fleet — giving three competing models one shared, durable, trustworthy memory (hardened in v1.16.7 so a working recall is visible, fresh, and never cites a file that no longer exists), a security perimeter around the hosted-model path, and a single place to watch and review everything they do. See the full, sourced feature-by-feature comparison vs Warp, Wave, JetBrains Air, and Tabby.
Every model has blind spots. Second Opinion hands the most recent answer in any AI terminal to a different agent for a fast, read-only critique — the feedback is pasted back into the same terminal, ready for you to send to your primary agent or just read and discard.
agy in plan 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.agy), its current headless entry point.Proven end-to-end. A CI test drives a real review from Claude, Codex, and Gemini (via
agy) against a deliberately-bad solution ("sort 1,000,000 items with bubble sort") and confirms each returns substantive feedback.
Long AI coding sessions chew through rate limits and hit compaction fast — and the biggest culprit 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 reaches Anthropic, so you get more work done per session before you hit a wall.
127.0.0.1 (its own process — never the UI/PTY thread), which forwards only to api.anthropic.com. No setup. The switch is in Settings → Token Savings (the tour's last step and the one-time privacy review offer it too): off means new sessions talk to Anthropic directly, and a session that is already running keeps its route until it ends. If you already route Claude Code yourself — your own ANTHROPIC_BASE_URL, an HTTPS_PROXY / HTTP_PROXY / ALL_PROXY (upper- or lower-case), or CLAUDE_CODE_USE_BEDROCK / CLAUDE_CODE_USE_VERTEX / CLAUDE_CODE_USE_FOUNDRY, set in Termpolis's environment or in the env block of Claude Code's own ~/.claude/settings.json — the proxy steps aside, and Settings → Token Savings names the variable it found: Claude Code keeps the route you configured, and nothing is compressed. If the proxy is ever unhealthy the launch silently goes direct, and a live session self-heals when the proxy recovers — so a proxy hiccup costs you compression, not your agent.retrieve_full tool to pull any compressed result back when it needs the detail.… 24 lines …. You keep the API surface of the whole file for a fraction of the bytes — this repo's own wireCompress.ts outlines from 22,139 characters to 4,450, a , and the line budget then bounds the block to 2,127 characters () when the file is larger than the window allows. It handles ~35 languages across both brace- and indentation-delimited families, sees through the gutter on a result so line numbers stay correct, and when the text isn't code or the outline wouldn't be meaningfully smaller. Structured content also gets of prose — a signature list is worth more per line than a log tail — while staying bounded and one away from the original.Built in-house, tested hard. Deterministic compressors, a source-level cache-safety guard (no clock or randomness anywhere in the compression path), mode-graded output steering (conservative/balanced/aggressive), fail-open at every layer, and an end-to-end proof measuring real token reduction with the cache intact — all under Termpolis's 8,400+ test suite.
The desktop app is where the work happens. Termpolis Remote is a small companion app for iPhone (Termpolis R on the App Store) that lets you read those terminals and type into them while you are away from the machine — nothing more. It is a pass-through, not a second Termpolis.
utilityProcess, so a busy relay connection can never make your terminals stutter.The wire format is specified in docs/remote-wire-format.md and implemented twice — src/main/remoteBridge/ on the desktop, mobile/src/wire/ on the phone — with a test that makes each side open what the other sealed on every CI run. See mobile/README.md to run the client.
Link two Termpolis desktops, and an agent on one can have a Claude, Codex or Gemini agent on the other do a task headlessly and hand back its final answer as an ordinary tool result. It works in both directions, across any network: "Have Claude on linux implement the parser in ~/repos/foo and commit it, then review the commit yourself." Nothing is typed into a terminal and no terminal opens on either machine. Every job is listed under Activity on both computers.
Setting it up, once per pair of computers:
How your agent decides to use another machine. Termpolis doesn't split your work or schedule anything across machines. Agents get one MCP tool, linked_machines, and use it the way they use any tool: when you ask (you refer to a machine by the name you gave it: "have codex on linux run the integration tests and summarise the failures"), or when the agent judges a task belongs on the other computer, say because Codex is blocked on this network or the repo, hardware or test environment is over there. It can list the machines to see which agents are installed on each and what it may do there, then run a task with a self-contained prompt and, optionally, a folder on that machine. The other agent's final message comes back as the tool result, and your agent reviews it like any other answer. A workflow you build can also call the tool as a step.
What each permission means. Each computer decides what the other may do on it, per linked machine, and you can change it at any time:
agy) in plan mode, each with this machine's memory and code index as read-only Termpolis tools and nothing else of Termpolis's. That agent can read any file you can read on this computer and send it back. Codex and agy keep MCP servers you added to their own configs.workspace-write sandbox: it changes files only in the job's folder and temporary folders, and Termpolis switches the sandbox's network off for the job (on Windows, treat a Codex edit job as full access too: whether Codex's sandbox there keeps it from the computer's own local services isn't confirmed). Grant it only to a machine you trust as much as this one; its row then shows full access. Switching it off stops any edit already running; it doesn't undo changes already made.The computer doing the work doesn't ask you per job, but it tells you: each job another computer starts there shows a system notification (on by default; click it to open Settings → Linked machines), every job and every refusal goes into its Job history, kept across restarts, and a running job can be stopped from Activity.
Limits.
run waits up to 45 seconds, within the 60 seconds Codex gives one tool call. A job still going returns running with a jobId, and the agent collects it with action: "result". A job runs for up to 15 minutes by default.Security.
wss:// relay (plain ws:// only to a relay on the same computer), for codes and for every reconnect, and the relay's source is in relay/ if you'd rather run your own.linked_machines included, and offering the job only the tools it may use, so it also carries fewer tool definitions. That binds read-only jobs: a Claude or Gemini job with edit permission has a shell and runs as you, so it can get around it and reach anything you can. A Codex edit job's commands run in Codex's sandbox with its network switched off.sk-ant-…, ghp_…, AKIA…, xox…, a private-key block and the like) is replaced with . Anything that only like a credential (a test fixture's , a URL, a JWT) is left in, because the answer is usually code, and the agent gets a note naming the rules that matched. Each finding is written to the AI Security audit log as rule ids and counts, never the value.Full guide: docs/DOCUMENTATION.md. Wire format: docs/remote-wire-format.md.
A Memory & Learning tab in Settings turns the brain from a black box into an inspectable instrument — every number computed on your machine, offline, from the append-only store. No word-taking; nothing on the screen leaves your machine.
int8 instead of exact floats is 4× less vector RAM. But a bare switch would be a trap — nobody can answer "should I enable int8 quantization?" in the abstract — so this is a decision aid, and the answer it gives almost everyone is don't: at a typical corpus (14k vectors ≈ 21 MB) it says "not needed — turning int8 on would free about 16 MB, which is not enough to change anything." A control that only ever markets itself is an upsell, not a tool. Off by default. Settings → Memory & Learning.These numbers are read when you open the tab and when you press Refresh — never on a timer (v1.25.16). Computing them means scanning the whole store, and that scan runs in the main process, which is the same thread that echoes your keystrokes into the PTY. On a 5-second poll it stalled typing every 5 seconds and generated enough garbage to drive the very GC pauses the dashboard used to display. A dashboard paid for out of your typing latency is not worth having. The freeze history that used to sit above this section is gone for a worse version of the same reason — see Diagnostics that cost more than they're worth, below.
On int8, two guarantees worth stating plainly. Recall parity against the exact-float baseline is benchmarked and CI-gated — the benchmark scores the quantized store against the same committed baseline as the float one on the real
bgemodel, and recall@10 is identical; a change that degraded it would fail the build. And it is losslessly reversible: the JSONL on disk always keeps exact floats, so int8 is purely an in-RAM representation, never a data migration. Turning it off restores full precision — nothing is ever destroyed.
The screenshot shows the dashboard's layout with representative sample data; your instance fills in with your own local numbers as you work.
v1.25.15 shipped a V8 sampling profiler so that a freeze in unlabelled code could still be named. The reasoning was sound and the measurement was not: Profiler.stop was clocked at 4–15 ms in a small test script, so it was called straight from the stall watchdog. In a real main process — 1.1 GB heap, 1.75 GB RSS, an enormous loaded-code footprint — that same call blocked the thread for ~1000 ms. And the watchdog called it on every stall it detected:
Profiler.stop blocks ~1000ms -> the next 250ms tick arrives 750ms late
750ms > the 400ms threshold -> "a freeze!" -> to name it, harvest -> Profiler.stop
...which blocks ~1000ms -> the next tick is 750ms late -> "a freeze!" -> ...
A closed loop, feeding itself, that never breaks: every freeze it detected was one it had just caused. One 21-minute session recorded 1,139 freezes blaming the profiler for 890 seconds of main-thread block — with no application work running at all. The main thread is the thread that echoes your keystrokes, so it surfaced as a 5–10 second typing lag and an app that froze on every click.
The lesson generalises past this bug, so it is worth writing down: an instrument whose cost you measured somewhere cheap, wired into the loop that reacts to that cost, is a positive feedback loop waiting for a big enough heap. The profiler, the freeze detector and the live vector-RAM tiles beside them — RSS, heap, GC pauses, event-loop percentiles, all polled every 2 s off the thread that echoes your keystrokes — are removed, and tests/electron/noMainThreadInstruments.test.ts now fails the build if the V8 inspector is ever attached to the main process again.
What survived is the part that never needed any of it. The int8 verdict is pure arithmetic on the vector count — count × 384 × 4 against count × 384 × 1 — so it is answered by one cheap read on tab-open, and it is back. What it can no longer do is claim to know whether that RAM is hurting you: the two verdicts that asserted a stalling main thread are gone with the instrument that measured one. A panel that cannot measure the harm does not get to assert it — so it reports the size, offers the toggle, and does not push.
Killing the profiler fixed the typing lag. It did nothing for the other one: on a large store the app froze for ~18 seconds at launch, painting "(Not Responding)" before it would take a keystroke. Three separate causes, all measured against a real 475 MB / 94,430-entry store in a real Electron main process — not a benchmark script, which is the mistake that produced v1.25.15.
1. Loading the knowledge graph took 7–10 seconds — from a 3.4 MB file. Not a data-volume problem; two compounding quadratics. Every {removeNode} marker in the append-log swept the entire adjacency map to find the edges pointing at one node — copying the whole map and allocating a fresh array per node — and the real log holds 1,625 of those markers. Meanwhile reverseAdjacency, an index whose entire purpose is to answer "who points at this node?" in O(1), was already being maintained and simply wasn't used here. (Separately, upsertEdge re-sorted a node's whole edge list on every insert, making a bulk load O(d² log d) per node; the sort only needs to happen once, at the end.) Fixed: 10,363 ms → 110 ms, a 94× speedup, producing a byte-identical graph — same 15,892 edges, same 11,240 nodes, same order, verified edge-for-edge against the old implementation on the real log.
2. The BM25 keyword index cost 445 MB and blocked the thread for ~5 s while it built. Its payload lived in Map<docId, Map<term,tf>> + Map<term, Set<docId>> — two JS collection entries per posting, each spending ~26–50 bytes of V8 bookkeeping to hold 4 bytes of information, 8.5 million times over. It now lives in flat Int32Array/Uint32Array runs: 4.8× less heap, ~550 MB saved, with BM25 scores that are bit-for-bit identical (verified across 218 queries on the real corpus, worst score delta exactly 0).
3. …and it still had to stop blocking. Packing the index made it cheaper to hold, not meaningfully cheaper to build — the build cost is hashing 16.4 million tokens, which no representation avoids. So the build was moved off the critical path: it now runs in the background, yielding to the event loop, and only a search that actually needs it ever waits. Launch no longer pays for it at all.
Measured end-to-end on the real store: launch went from ~18 s of dead main thread to ~4 s, and the index build that remains yields the event loop hundreds of times instead of zero. The yield is setImmediate, deliberately — await on an already-resolved promise queues a microtask, and Node drains the microtask queue to completion before the loop turns, so it does not yield at all. That is not a hypothetical: it was the v1.25.11 freeze, 2,777 ms of unbroken work with zero event-loop turns and the same wall-clock time as the version that yielded properly.
The tests are the point, though. Every previous test of this code passed throughout the entire period the app froze for eighteen seconds, because they all built the index over a handful of documents — where it finishes inside the first chunk and never yields at all. A test for a freeze that never freezes anything proves nothing. tests/electron/lexicalBackgroundBuild.test.ts forces the yielding path and watches the event loop with a self-rescheduling setImmediate counter: against the old synchronous build it ticks zero times, and all eight tests go red.
A note on AI-assisted development: There may be critique that this application is built in conjunction with using AI; however, if you are still exclusively using an IDE or manually writing every line of code, then you are doing it wrong. This is the new path for AI-native engineering as a programmer. Code review is often still needed, but beyond this, software engineering has a new path. Termpolis itself is built with AI and built for AI workflows — and that's the point.
Support this project — Termpolis is free and open source. If you find it useful, consider sponsoring the project to help cover AI token costs and development time.
Full docs with screenshots: termpolis.com/docs — or see docs/DOCUMENTATION.md in this repo. Covers every feature: terminals, splits, the swarm dashboard, AI conductor, activity feed, intervention controls, shared memory, MCP server, and the full keyboard shortcut reference.
| Platform | Download | Format | Signed |
|---|---|---|---|
| Windows | Termpolis Setup.exe | NSIS Installer | Code signed (SSL.com) |
| macOS (Apple Silicon) | Termpolis-arm64.dmg | DMG | Signed & notarized (Apple) |
| macOS (Intel) | Termpolis-x64.dmg | DMG | Signed & notarized (Apple) |
| Linux (Debian / Ubuntu) | termpolis_*.deb | .deb | — |
| Linux (other distros) | Termpolis.AppImage | AppImage | — |
The Windows installer is code signed via SSL.com and the macOS DMG is signed and notarized with Apple Developer ID — both platforms will recognize Termpolis as a verified application. Download links point to the latest GitHub Release. See Building from Source to compile locally.
Install it with apt, in the folder you saved it to. apt also installs anything it depends on that is missing (libgtk, libnss3, …):
sudo apt install ./termpolis_*.deb
From a folder in your home directory apt prints a notice that the download "is performed unsandboxed as root" because its _apt user can't read the file ("pkgAcquire::Run (13: Permission denied)"). That is only a notice: the install goes ahead. sudo dpkg -i ./termpolis_*.deb works too, but installs no missing dependency; if it says any are missing, run sudo apt-get install -f after it.
The package refreshes the desktop and hicolor icon caches, so the launcher icon shows up without a logout. Its .desktop entry passes --no-sandbox on its Exec= line, so clicking the dock icon starts the app even where Chromium's sandbox can't (Ubuntu 24.04 restricts the user namespaces it needs).
Updates. Termpolis downloads updates itself, and the banner offers Restart now once one is ready. Since v1.53.1, Restart closes Termpolis at once, your desktop asks for your administrator password once, and Termpolis opens again on the new version; if you cancel the password dialog, it opens again on the version you had and says so. Quitting Termpolis no longer installs an update, or asks for your password. The update from 1.53.0 to 1.53.1 still runs 1.53.0's installer: the window stops responding while the password dialog is up and the install runs, and your desktop may offer to Force Quit it. Click Wait, and Termpolis restarts on 1.53.1. Cancelling that dialog brings a second one (1.53.0 then tries apt-get install -f): cancel both to stay on 1.53.0. Quitting 1.53.0 once the update has downloaded asks for your password too, after the window has closed, and installs 1.53.1. Having cancelled, you can install 1.53.1 from a terminal with sudo apt install ./termpolis_1.53.1_amd64.deb. More in Troubleshooting.
If you ever need to launch from a shell with the same flag: /opt/Termpolis/termpolis --no-sandbox. If the window opens blank or black (some NVIDIA / Wayland drivers), start it with TERMPOLIS_DISABLE_GPU=1 to turn the GPU off.
sudo in Termpolis terminals. If sudo, su or pkexec fail in every terminal with "The 'no new privileges' flag is set", the window was reopened by an in-app update of the .deb from Termpolis 1.50.0 or earlier. Quit Termpolis and open it again from your applications menu. Since v1.50.1, updates no longer cause it, and a window that starts with the flag says so and, where systemd-run is available, offers to restart itself properly. Every Termpolis terminal also sets SUDO_ASKPASS, so sudo -A there asks for your password in a Termpolis dialog that shows the whole command (Linux and macOS). Termpolis tells Codex, and Claude Code in projects with saved memory, to use it; otherwise mention sudo -A in your prompt.
* Windows SmartScreen note: SmartScreen may show a "Windows protected your PC" warning for newly signed software. Click "More info" then "Run anyway" to proceed. Termpolis is digitally signed and safe to install — the warning disappears as download reputation builds.
sudo (Linux, macOS) — sudo -A in a Termpolis terminal asks for your password in a dialog that shows the whole command about to run. Type it only for commands you expected: whatever you approve runs as administratorCtrl+K opens a natural language command bar to control the app (new terminal, split panes, launch agents, run commands)Ctrl+Shift+P (accessible via Command Palette)when gates and continueOnError; later steps read earlier results (steps.build.exitCode, captured output) through a sandboxed expression engine (no eval); and a live Runner timeline streams every step and lets you cancel mid-run0 2 * * *, or @daily-style aliases — evaluated in local time, with catch-up so a run missed while the app was closed still fires once), Git commit (fires after a commit lands on the checked-out branch — a post-commit hook that can lint, test, or hand the diff to an agent), Git push (the remote-tracking ref moves), or File change (a debounced recursive watch, optionally filtered to a path prefix). Triggers are watched in the main process with no polling child processes, survive restarts (last-seen state is persisted per project), and every automatic run takes the exact same path as pressing Run — same workspace-trust gate, same run history, same live timeline. Untrusted folders never fire.termpolis/workflows and travel with the code). Give it a to file it into a sidebar folder, and declare to point the same workflow at a different target each run — every input is substituted as into commands, prompts, skill arguments and gates, alongside the automatic , and . Required inputs are collected before the Run button unlocks, so a run never starts half-configuredlocalhost:9315 with tools for AI agents to control terminals programmatically (incl. shared-memory search/write/list, the background primer, memory_related traversal, the knowledge graph memory_link + memory_graph, the learning tools memory_anticipate / memory_pool / memory_selfcheck / memory_feedback / memory_conflicts, the memory_audit self-inspection tool, the code-graph tools code_explore / code_callers / code_callees / code_impact / code_search / code_locate, and linked_machines, which has a Claude, Codex or Gemini agent on another of your linked machines do a task headlessly and hands back its final answer)Termpolis --disconnect-agents does it from the command line.termpolis-cli lets you control Termpolis from any terminal (list, create, run, read, close, files, git) and reach the shared memory brain from a plain shell, CI job, or git hook (primer, recall, remember)No AI company has built a tool that brings together competing models to work as a team — because it helps their competitors. Termpolis does it anyway, because it moves AI forward.
AI Conductor — a dedicated Claude Code instance runs as the swarm conductor. It receives your task description, reasons about how to break it into subtasks, assigns each subtask to the best agent via MCP tools, and monitors completion. This is live AI orchestration — not keyword matching.
Smart Task Routing — the conductor assigns subtasks to the best agent based on a customizable capability matrix. Scores are transparent (0-100) with human-readable reasons explaining every assignment. Token-heavy work is routed to cheaper agents for cost efficiency. Every assignment can be manually overridden. Default ratings are estimates based on general model capabilities — customize them in Settings → Agent Ratings based on your experience. The conductor uses ratings as hints but makes its own judgment.
| Capability | Claude Code | Codex | Gemini CLI |
|---|---|---|---|
| Refactoring | ★★★★★ | ★★★★ | ★★★ |
| Testing | ★★★★ | ★★★★★ | ★★★ |
| Documentation | ★★★★ | ★★★★ | ★★★★★ |
| Code Review | ★★★★★ | ★★★ | ★★★★ |
| DevOps/Infra | ★★★ | ★★★ | ★★★★★ |
| Bulk Tasks | ★★★ | ★★★★ | ★★★ |
| Token Cost | $$$$ | $$$ | $$ |
Swarm Wizard — 3-step flow: prepare conductor → describe task → launch. Includes guidance on when to use a swarm (autonomous task completion) vs individual agent terminals (back-and-forth conversation). Live progress tracking shows conductor status in real time — the modal stays open until the first task or message appears (can take up to 30 seconds).
Agents run in the background — swarm-spawned agent terminals are hidden from the sidebar. The conductor drives all work via MCP tools (creating files, running commands, coordinating agents) and posts progress to the dashboard. For back-and-forth conversations, launch individual agents from the AI Agents sidebar section — those still appear in the sidebar and work normally.
When you're running multiple AI agents concurrently (or a whole swarm), you need to see what each is doing, spot when they duplicate work, and know when one is about to run out of context. Termpolis ships a full observability layer that doesn't require any external dashboard — everything is local, capped in memory, and tested end-to-end.
Ctrl+Shift+A opens a live stream of every agent event. Captures messages, tool calls, tool results, token updates, compaction events, errors, status changes, and MCP audit entries. Filter by agent (claude/codex/gemini), by kind, or search full text. Newest first.Ctrl+Shift+D shows duplicate work across terminals. If two agents are running npm test at the same time or both editing the same file, you'll see a severity-ranked finding with the affected terminals.Ctrl+Shift+Y aggregates per-agent stats: token totals, cost, error rate, average tool-call duration. Spot when one agent is burning budget while another is cruising.Ctrl+Shift+S) now shows live token burn per agent, tasks in kanban columns, and the full conductor message log. Every panel streams from the same event bus — no polling lag.@@ hunk headers). Auto-detects git repos from the active terminal's working directory, or lets you pick a folder (VS Code-style). Collapsible sections, auto-refreshes every 3 seconds.cd in every supported shell, and no longer takes the ~ in an ssh or WSL prompt inside the terminal for your own home folder, unless that prompt looks like your own shell's (in a zsh terminal, a prompt ending in % or # still does); polling is keyed by repository, so ten terminals on one repo cost one git status.VITE_SENTRY_DSN and SENTRY_DSN env vars to enable it in a build. Crash reports and anonymous usage statistics are then two separate choices, both off until you tick them (the tour's last step, or Settings → General → Privacy). A report drops your user name, home-folder paths and machine name, and never carries a minidump or a screenshot.All shortcuts are customizable in Settings → Keybindings.
| Shortcut | Action |
|---|---|
Ctrl+K | Command palette |
Ctrl+Shift+T | New terminal |
Ctrl+Shift+W | Close terminal |
Ctrl+Tab | Next terminal |
Ctrl+Shift+Tab | Previous terminal |
Alt+1 – Alt+9 | Jump to terminal by number |
Ctrl+Shift+H | Search command history |
Ctrl+Shift+C | Copy selection |
Ctrl+Shift+V | Paste |
Ctrl+Shift+Space | Keyboard select / copy mode (arrows move · Shift extends · Ctrl=word · Enter copies) |
Alt+Shift+Click | Anchor select — click a start, scroll anywhere, click the end: everything between is selected + copied. Unlike dragging, it spans any amount of scrollback. Any plain click cancels. |
Ctrl+B | Toggle sidebar |
Ctrl+Shift+G | Toggle split view |
Ctrl+Shift+X | Clear terminal — screen and scrollback (nothing is sent to the process, so an agent keeps its own context) |
Ctrl+Shift+O | App log — what Termpolis itself has printed, secrets redacted on write |
Ctrl+Shift+P | Prompt templates |
Ctrl+Shift+E | Smart context panel |
Ctrl+Shift+I | Conversation history search |
Ctrl+Shift+S | Swarm dashboard |
Ctrl+Shift+A | Activity feed (agent events) |
Ctrl+Shift+D | Redundancy panel (duplicate work) |
Ctrl+Shift+Y | Efficiency panel (per-agent stats) |
Win+Shift+T | New terminal (global, works when minimized; Ctrl+Option+T on macOS) |
On macOS, use
Cmdinstead ofCtrl.
Termpolis runs an MCP (Model Context Protocol) server on localhost:9315 that AI agents can connect to via HTTP/SSE.
Once you connect your agents — on the first step of the first-run tour, or in Settings → Agent Integration — Termpolis registers itself as a user-scope MCP server: in Claude Code's ~/.claude.json (as claude mcp add -s user writes it), Codex's config.toml, the Antigravity CLI's ~/.gemini/config/mcp_config.json (with permission for the same read-only and memory tools Claude Code gets, in ~/.gemini/antigravity-cli/settings.json) and Gemini CLI's settings.json. Nothing is written before you agree, and Disconnect removes all of it.
Terminal Management:
| Tool | Description |
|---|---|
list_terminals | List all open terminals with IDs, names, shells, and cwds |
create_terminal | Create a new terminal with name, shell, and working directory |
run_command | Send a command to a terminal (types it and presses Enter) |
run_and_wait | Run a command to completion and return its exit code and output |
read_output | Read recent output from a terminal (last N lines) |
write_to_terminal | Write raw text to a terminal. With submit: true it waits until the agent in it is ready for input (up to 3 minutes), types the text, waits until the agent shows it, then presses Enter on its own and checks the agent took it; on a failure it returns the reason and the screen |
close_terminal | Close a terminal by ID |
get_file_tree | List files and directories at a path |
get_git_status | Get git status, branch, and recent commits |
Swarm Coordination:
| Tool | Description |
|---|---|
swarm_send_message | Send a message to another agent or broadcast to all |
swarm_read_messages | Read unread messages addressed to you |
swarm_create_task | Create a task and optionally assign to an agent |
swarm_list_tasks | List all tasks with statuses |
swarm_update_task | Update task status and report results |
swarm_list_agents | List all active terminals/agents |
Shared Memory & Learning:
| Tool | Description |
|---|---|
memory_search | Semantic + keyword search across the shared memory brain |
memory_write | Persist a fact, decision, or note for every agent to recall |
memory_list | List recent memory entries with filters — pass project (your cwd) for "what did we do here last?" |
memory_primer | Load a background-memory digest for the current project: relevance-ranked, with reserved newest-first slots |
memory_related | 1-hop traversal from a memory entry (or query) to its neighbours |
memory_link | Record a typed edge between two memories (knowledge graph) |
memory_graph | Multi-hop walk of the knowledge graph from a seed memory |
memory_anticipate | Surface lessons the fleet already found for a task before you start |
memory_pool | Pool cross-agent lessons multiple agents independently arrived at |
memory_selfcheck | Report the brain's calibrated self-competence in a domain |
memory_feedback | Mark a recalled memory as helpful so the useful ones rank higher |
memory_conflicts | Surface pairs of lessons different agents learned that assert opposite things about the same subject |
memory_audit | Inspect the brain's own behaviour — what it stored, recalled, and learned, computed from the local store |
memory_correct | Retract, amend or demote a memory that recall got wrong; nothing is deleted, and the reason is kept with it |
Code Intelligence (the code graph is AST-precise via web-tree-sitter, native-free):
| Tool | Description |
|---|---|
code_explore | A symbol's source plus its direct callers and callees, in one call |
code_callers | List the symbols that call a given symbol |
code_callees | List the symbols a given symbol calls |
code_impact | Transitive blast radius of changing a symbol — what could break |
code_search | Locate any symbol by name across the codebase |
code_locate | Predict WHERE an issue lives: ranked {file, symbol, why:[past lessons]} from an error/problem — crosses the memory↔code bridge |
test_coverage | Which lines of a file its own tests cover, from the project's last coverage run |
Gateway:
| Tool | Description |
|---|---|
gateway_list_tools | List the tools on the MCP servers you added in Settings → MCP Servers |
gateway_call | Call one of those tools, under the gateway's policy |
Token Headroom:
| Tool | Description |
|---|---|
retrieve_full | Expand a tool result that Token Headroom shortened |
Linked Machines:
| Tool | Description |
|---|---|
linked_machines | Have a Claude, Codex or Gemini agent on another linked machine do a task headlessly and return its final answer (list, run, result) |
Control Termpolis from any terminal without the MCP protocol:
termpolis-cli health # Check if MCP server is running
termpolis-cli list # List all open terminals
termpolis-cli create "Dev" bash # Create a new terminal
termpolis-cli run <id> "npm test" # Run a command in a terminal
termpolis-cli read <id> 20 # Read last 20 lines of output
termpolis-cli close <id> # Close a terminal
termpolis-cli files ~/projects # List files at a path
termpolis-cli git ~/projects/myapp # Get git status
# Shared memory — the same brain every Termpolis agent reads and writes.
# Reachable from a plain shell, so a CI job, a git hook, or a script can use it too.
termpolis-cli primer # Print the project primer for the current directory
termpolis-cli recall "auth retry bug" # Search the shared memory brain
termpolis-cli remember "Deploys need VPN" # Write one memory to the shared brain
The MCP server requires a Bearer token on all endpoints except /health. A random token is generated on each app launch and written to:
| Platform | Token file |
|---|---|
| Windows | %APPDATA%\termpolis\mcp-token |
| macOS | ~/Library/Application Support/termpolis/mcp-token |
| Linux | ~/.config/termpolis/mcp-token |
# Read token and make an authenticated request
TOKEN=$(cat ~/.config/termpolis/mcp-token)
curl -H "Authorization: Bearer $TOKEN" http://localhost:9315/mcp \
-d '{"jsonrpc":"2.0","method":"tools/list","id":1}'
curl http://localhost:9315/health
# {"status":"ok","name":"termpolis-mcp","version":"1.2.0","tools":40,"auth":"required"}
Termpolis takes security seriously, especially with AI agent integration.
127.0.0.1, never exposed to the networkAuthorization: Bearer header on all endpoints except health check600 (owner read/write only) on macOS/Linux; on Windows, an NTFS ACL that drops the access inherited from the folder and grants your account full control (Everyone, Users and Authenticated Users get nothing; entries set on the file itself, which can include SYSTEM and Administrators, who can read any file anyway, are left alone)contextIsolation: true with nodeIntegration: false, all main process access via secure contextBridgeeval(), no remote module loading, no webSecurity bypasses~/.claude, ~/.codex, ~/.gemini), which is exactly where you'd have put it by hand. Termpolis's own attack surface is unchanged.PRIVACY.md for the full data-flow spec.)node-pty compilation)build-essential, python3 (for native module compilation)git clone https://github.com/codedev-david/termpolis.git
cd termpolis
npm install
npm run dev
npm install also fetches the Electron binary: since Electron 42 the electron package no longer downloads it by itself, so the postinstall script runs Electron's own install-electron to do it. With install scripts turned off (--ignore-scripts), Electron downloads it the first time the app starts instead.
npm test
6,700+ total tests:
npm test — 6,700+ unit & component tests (Vitest, 336 test files, 95%+ coverage)npm run test:coverage — unit tests with v8 coverage reportnpx playwright test — 75 E2E tests (Playwright, launches the actual Electron app)e2e/screenshots/bash scripts/download-tools.sh # Download bundled CLI tools
npm install
npm run package
Output: dist-electron-builder/Termpolis Setup X.X.X.exe
bash scripts/download-tools.sh
npm install
npm run package
Output: dist-electron-builder/Termpolis-X.X.X-arm64.dmg and Termpolis-X.X.X-x64.dmg
macOS builds must be run on macOS. Both Apple Silicon (arm64) and Intel (x64) DMGs are produced.
bash scripts/download-tools.sh
npm install
npm run package
Output: dist-electron-builder/Termpolis-X.X.X.AppImage
Linux builds must be run on Linux.
The project includes a GitHub Actions workflow (.github/workflows/release.yml) that builds for all three platforms on tag push:
git tag v1.2.0
git push --tags
The workflow automatically downloads the latest bundled CLI tools before packaging.
| Layer | Technology |
|---|---|
| Framework | Electron 43 |
| Build Tool | electron-vite |
| Renderer | React 18 + TypeScript |
| Terminal Emulator | xterm.js 5 + addons (fit, unicode11, web-links) |
| Shell Process | node-pty |
| State Management | Zustand |
| Code Editor | Monaco Editor (lazy-loaded) |
| MCP Server | HTTP/SSE on localhost:9315 |
| Icons | Font Awesome 7 |
| Styling | Tailwind CSS 4 |
| Testing | Vitest + React Testing Library |
| Packaging | electron-builder |
termpolis/
├── src/
│ ├── main/
│ │ ├── index.ts # App entry, IPC handlers, single-instance lock, MCP server
│ │ ├── mcpServer.ts # MCP protocol server (HTTP/SSE, JSON-RPC 2.0)
│ │ ├── agentCommandSanitizer.ts # Swarm agent command enforcement (allowlist + flag stripping)
│ │ ├── terminalManager.ts # node-pty wrapper + bundled tools PATH injection
│ │ ├── completionService.ts # PATH scanning, file listing, env vars for autocomplete
│ │ ├── shellDetector.ts # OS-aware shell discovery
│ │ ├── sessionStore.ts # JSON session persistence with migration
│ │ ├── historyStore.ts # Cross-terminal command history
│ │ ├── configFileManager.ts # Read/write shell config files
│ │ ├── workflow/ # Workflow Orchestrator engine (steps, sandboxed expr, YAML store, IPC,
│ │ │ # cron parser + schedule/git/file trigger supervisor)
│ │ └── types.ts # Main process type definitions
│ ├── preload/
│ │ └── index.ts # contextBridge API + MCP event bridge
│ └── renderer/src/
│ ├── App.tsx # Root layout, session restore, global shortcuts
│ ├── store/
│ │ └── terminalStore.ts # Zustand state (terminals, workspaces, pane tree, AI state)
│ ├── lib/
│ │ ├── agentDetector.ts # Detect AI agents from terminal output
│ │ ├── costTracker.ts # Parse token/cost from AI agent output
│ │ ├── conversationParser.ts # Parse AI conversations from terminal output
│ │ ├── sessionRecorder.ts # Session recording buffer + export
│ │ ├── promptParser.ts # Parse cwd and git branch from prompt output
│ │ ├── keybindings.ts # Keybinding types, defaults, matching utilities
│ │ ├── outputThrottle.ts # rAF-based write batching with 64KB rate limit
│ │ ├── exportTerminal.ts # Buffer extraction + ANSI stripping
│ │ └── terminalDefaults.ts # Default fontSize, theme, fontFamily
│ ├── themes/
│ │ └── terminalThemes.ts # 7 curated xterm ITheme definitions
│ ├── completions/ # Autocomplete engine, input parser, spec loader, 20 specs
│ ├── corrections/ # Command correction engine + rules
│ └── components/
│ ├── Sidebar/ # Terminal tabs, AI profiles, git panel, workspace list, collapse
│ ├── SplitView/ # Split pane layout with draggable dividers
│ ├── TerminalPane/ # xterm.js terminal with all integrations
│ ├── CommandPalette/ # Natural language command bar (Ctrl+K)
│ ├── PromptTemplates/ # Reusable prompt snippets (Ctrl+Shift+P)
│ ├── Workflow/ # Workflow Orchestrator UI (designer, runner, sidebar)
│ ├── ContextPanel/ # File tree, git status, recent commits
│ ├── ConversationSearch/ # AI conversation history search
│ ├── DiffViewer/ # Syntax-highlighted diff rendering
│ ├── PinnedOutput/ # Persistent pinned output panel
│ ├── CompletionDropdown/ # Autocomplete dropdown overlay
│ ├── CommandFix/ # Inline correction banner
│ ├── StatusBar/ # App footer + per-terminal status bar
│ ├── SettingsPane/ # Settings + keybindings + Monaco config editor
│ └── HistorySearch/ # Command history search modal
├── tests/ # Vitest test suites (7,000+ tests, 344 files, 97%+ coverage)
├── scripts/
│ └── download-tools.sh # Download latest jq, yq, nano per platform
├── resources/tools/ # Bundled CLI tool binaries (per platform)
└── .github/workflows/release.yml # CI/CD: build all platforms on tag push
Session data is stored as JSON in the Electron userData directory:
%APPDATA%/termpolis/session.json~/Library/Application Support/termpolis/session.json~/.config/termpolis/session.jsonSaved state includes: workspaces with working directories, default shell, view mode, keybindings, AI profiles, and prompt templates.
Loose terminals are deliberately not restored. Every launch starts on a clean slate. Auto-restore resurrected shells whose processes were long dead and competed with workspaces for ownership of "which terminals are open" — saving a group of terminals for a project is a workspace's job, and a workspace is restored when you open it, never silently at boot.
Old sessions are automatically migrated — missing fields receive defaults on load.
Termpolis is free, open source, and Apache 2.0 licensed. Building and maintaining it (including AI token costs for development) takes time and resources.
If you find Termpolis useful, please consider sponsoring:
Found a bug or have an idea? Open an issue on GitHub:
When reporting a bug, please include:
package.json)git checkout -b feature/my-feature)npm test (unit) and npx playwright test (E2E)git commit -m 'feat: add my feature')git push origin feature/my-feature)Apache License 2.0 — see LICENSE and NOTICE. Companies are free to use, modify, and redistribute Termpolis, including in commercial products, with attribution.
--no-verify| Secret gets remembered — persisted into the shared brain | Memory-at-rest scrub (v1.25) — secrets are redacted before a memory is hashed, embedded, or written to disk, so a key sitting in a transcript or an indexed source file never lands in the brain and can never be recalled back into an agent's context later. On by default. | Applies at write time — it redacts what's being stored now, not what a pre-v1.25 store already holds. Regex-shaped secrets only. |
Whole .env or source file pasted | Code-chunk + env-dump detectors (v1.11.52) flag prompts >2 KB that look like code (indentation + braces + keywords) or contain 5+ KEY=value lines. The renderer surfaces a notice + audit entry. | Heuristic — false negatives possible on minified or unusual code shapes. The prompt is not blocked; you decide. |
| Free-tier Gemini sending prompts to Google for product improvement | Gemini account-mode auto-detection reads GEMINI_API_KEY / GOOGLE_GENAI_USE_GCA / GOOGLE_APPLICATION_CREDENTIALS+GOOGLE_CLOUD_PROJECT to classify the active session. Strict Mode intercepts gemini launches that look free-tier and refuses to forward them. Blocked launches are audited. | Detection is env-var based; if you ship credentials some other way the heuristic can't see, it can't classify them. |
| Provider quietly changes their ToS / data-controls page | Weekly ToS drift watcher (v1.11.52) GitHub Action fetches the three provider pages we cite (Anthropic, OpenAI, Google), normalizes the HTML, hashes it, and opens a tracking issue when the hash changes — so the docs in this repo stay aligned with what the providers actually publish. | Detects rendered-text changes, not legal intent. A human still reads the diff. |
| Agent silently talking to an unexpected endpoint | Egress audit (v1.11.52) can record each unique remote host:port an AI agent's PID holds open, read with netstat (Windows) / ss (Linux) / lsof (macOS). Nothing in the app runs that poll (v1.54.3 says so in the Security panel too): endpoint-security tools read repeated connection listing as network discovery. As of v1.25 that record is a policy: Egress Guard judges every observed endpoint against a published allowlist of AI-provider domains and raises anything else as a violation in the audit trail. Suffix matching is dot-anchored (an exact host, or a true dot-delimited subdomain of one), so evil-anthropic.com and anthropic.com.evil.net do not pass as Anthropic. Loopback and LAN addresses are never violations — a local model server is not exfiltration. On by default. | Polling, not packet capture — sub-minute bursts can be missed. No payload inspection. It flags; it never kills a connection. The poller only yields IP literals, so the allowlist is forward-resolved — when DNS is unavailable the guard stays silent rather than reporting every provider IP as exfiltration. |
| A third-party skill / plugin / MCP server you import | Safe Import (v1.25) — a skill, plugin, slash-command, subagent, or MCP server is not data: it's code plus instruction text handed to an agent that already holds your credentials, your repo, and a live PTY. Termpolis statically scans it before it touches your machine — 41 rules across outbound network calls, shell / eval execution, credential + ~/.ssh access, obfuscated payloads, and prompt injection hidden in the artifact's own instructions (tool poisoning — no dangerous API call appears anywhere; the agent is the exploit), plus context-sensitive checks that judge a construct by what surrounds it: a base64 decode feeding an execution sink is red, a decode on its own is yellow. You get a red / yellow / green report with file:line and the offending line. Red can never be installed — the refusal is enforced in the main process, not the UI. Approvals are hash-pinned, so editing an approved artifact re-prompts (no trust-on-first-use-then-swap). Zip-slip and TOML injection are refused by the installer. | A static review aid, not a sandbox — nothing is executed, in a jail or otherwise. It is line-based, and a determined attacker can obfuscate past any of it. Its job is to put the three lines that matter in front of you before you click Import — not to prove the artifact is safe. |
| Tampering surface beyond the terminal itself | No browser extension, no IDE plug-in, no ad-hoc cloud sync. The MCP server is bound to 127.0.0.1 with a token that rotates on every restart. No Termpolis telemetry unless you opt in, no Termpolis cloud accounts. | Termpolis is itself an Electron app — same caveats apply as any local desktop process running with your privileges. |
| Forensic record of what was typed at agents | Local JSONL audit log: every AI terminal open/close, every secret observed leaving in a prompt (prompt_secret_sent — names and rule ids only, never values), every code-chunk / env-dump detection, every Strict-Mode block — plus, as of v1.25, every blocked commit/push, every import scan and refusal, and every egress violation. 10 MB rotated, append-only, on disk only, wipeable. On by default as of v1.25 (it previously defaulted to off, so for most installs it never existed). | Local. We don't ship it anywhere. If your machine is compromised, so is the log. |
memory_primer) instead of a wall of pasted text. Cooldown-guarded to fire once per compaction; opt-out in Settings. Your durable memory is the large working set; the model's window only holds the active task.token_update stream) or a clearly-labeled heuristic otherwise. So you watch the pressure build and know recovery is handled, instead of being surprised by a compaction.🧠 Loaded N memories for "<project>" on success, or a clear ⚠️ Memory recall unavailable if the brain couldn't be reached — so you always know whether context loaded. Four reliability fixes back it up: a fast indexer tier makes your current conversation searchable within seconds instead of waiting for the next 30-minute pass; recall flags any code reference whose file has since been deleted as ⚠ STALE — verify before use instead of asserting it 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 agent gets the relevant slice, never padding.explains edge — the code ↔ purpose bridge (v1.25). A semantic memory now links directly to the code chunk it explains, so the prose that says why a thing works the way it does hangs off the code itself. The edge is gated on both embedding similarity and a shared file/symbol anchor — a merely chatty neighbour can never claim to explain code it has no anchor into. In the same pass the weaver was fixed to actually mint what it mined: intra-repo analogies are now allowed (it was cross-repo only, so the most useful connections — the ones inside the repo you're in — were being discarded), and the similarity floor drops 0.82 → 0.72.| Coordinating parallel agents | You juggle terminals by hand | Real-time observability — activity feed, redundancy detector, efficiency panel, and a swarm dashboard |
cat -nReadretrieve_full12345678901234567890 silently becomes 12345678901234567000, and a corrupted ID the agent believes is far worse than an uncompressed one.retrieve_full used to resolve out of a small in-memory cache that a busy session evicted within minutes — and every miss made the agent re-run the tool and pay full price twice. The cache is now disk-backed, byte-capped, and survives restarts, so a token issued yesterday still expands today.tool_use input — what the model asked the tool to do: the Bash command, the file body in a Write, both sides of an Edit, a subagent prompt. v1.34 compressed both halves, and v1.37.1 stopped touching file bodies and commands. Since v1.49 the proxy never rewrites a tool call's input, at any age — not on the live wire, not when old history is aged out — because the model replays its own earlier inputs as templates, and an elided one would land in a real file, a real command or a subagent's prompt. It compresses tool results (and pasted images) only. Earlier inputs are still read, never changed, so a result that reads back a file the agent just wrote still collapses.tool_use payloads — measured before v1.49 stopped compressing tool_use input), median request 61.4%, and 93.8% of requests clear 50%. At the Maximum tier: 72.3%, with 99.9% of requests clearing 50%. A regression test now fails the build if a threshold is ever loosened.retrieve_full away. The cutoff advances only at doublings (128 → 256 → 512 messages), so a 300-turn session pays at most a handful of breaks instead of one per turn. Since v1.49 it never ages out the result of a retrieve_full or memory call — the content the agent just asked for — and it leaves a <system-reminder> riding inside a tool result in place. Turn it off with Age out old history in Settings → Token Savings.[REDACTED:<rule>]password = "…"postgres://user:pass@--no-session-persistence, codex exec --ephemeral), and the memory indexer and learning reflex skip a delegated session that was saved anyway. What 1.50 to 1.52 already stored from such jobs is demoted once, whole sessions at a time: still stored and still found, ranked lower.${inputs.NAME}when${project.cwd}${project.name}${project.branch}git diff output and renders it with syntax highlighting (green/red for additions/deletions)Ctrl+Shift+E opens a side panel showing file tree, git status, and recent commits for the current directoryCtrl+Shift+I searches across all AI agent conversations indexed from terminal outputCtrl+Shift+L to start/stop hands-free, or hold to talk; the hotkey and the send key are rebindable--model) or switch a running agent's model from the terminal header, with savings hints — Sonnet ≈40% cheaper than Opus, Haiku ≈80% — so routine work runs cheap while hard work stays on Opus. Each terminal lists its own agent's models: Claude's aliases (always the newest in each family), Codex's from Codex's own model cache, Gemini's from agy models — discovered at launch and cached for 12 h, so an offline start keeps the last-known list. In a terminal Termpolis launched, a switch restarts the agent on the new model and resumes the conversation; a Claude session you started by hand gets a live /model instead (hand-started Codex and Gemini sessions get no picker, since switching means restarting them)bug → solved-by → fix, decision → supersedes → …), built explicitly via memory_link and automatically as curated memories are written. Agents follow the chain with memory_graph to reuse prior solutions fast instead of re-deriving them — the graph gets denser, and the agents get smarter, the more you use itcode_explore / code_callers / code_callees / code_impact (change blast-radius) / code_search / code_locate (predict where an issue lives) instead of grepping the same files repeatedly. Deep support for TypeScript/JavaScript, Python, Go, Rust, Java, C#, Ruby, and Swift; Terraform and Bicep fall back to a regex heuristic for symbol discovery. As of v1.23 the graph is keyed per repository — opening a second repo no longer clobbers the first, and because each repo's graph is a durable on-disk store, a transient non-git directory (or git off the PATH) won't wipe one you've already built. As of v1.23.1, an edit re-indexes just the changed file (debounced, AST-first) rather than re-sweeping the whole tree, backstopped by a periodic full re-index. Auto-indexed (opt-out in Settings), with an in-app Code Graph browser (Ctrl+Shift+M)eval execution, credential + ~/.ssh access, obfuscated payloads, and prompt injection hidden in the artifact's own instructions, plus context-sensitive checks that judge a construct by its surroundings. You get a red / yellow / green report with file:line; red can never be installed, and approvals are hash-pinned, so editing an approved artifact re-prompts. On approval it wires the artifact into the agents that support its kind — an MCP server into all three (Claude Code, Codex, Gemini CLI), custom commands into Claude / Gemini, and skills, subagents, and plugins into Claude Code. Settings → General. It is a static review aid, not a sandbox — nothing is executedSwarm Complete Dialog — when all tasks finish, a summary dialog appears showing completed vs failed tasks with the result from each agent. Includes "What next?" guidance for iterating with individual agents or starting a new swarm.
Usage-limit reports — when a worker's CLI shows that its plan's usage limit stopped it (Claude Code's rate-limit menu, its wait for the reset or its limit message under the prompt it stopped, Codex's limit message and workspace-limit prompts, Antigravity's quota message, Gemini CLI's quota dialog) or waits on a menu (Codex's "switch to a cheaper model?"), the dashboard says why, and the conductor is told once per stop, to check the worker's screen first: to close a worker a limit stopped and give its task to another installed agent, or which option of a menu to pick, which a key press or two does. Only what each CLI draws in its own rows and cells counts, in place of its input box, on its status line, or as the last thing above its input box when it ends a turn on its limit: never the same words elsewhere, so not a limit quoted in an answer, or in a file or a log a worker shows as it works.
Swarm Review Panel — a swarm can create a brand new project or modify an existing one. When it finishes, click Review Changes to open a per-hunk diff viewer showing the full delta from the pre-swarm HEAD. Accept or reject individual hunks (or entire files), run the project's test command against the result, then commit only the changes you want. git reset --hard back to the pre-swarm SHA cleanly reverts everything.
Agent Command Enforcement — agents are guaranteed to launch correctly regardless of what the conductor attempts. A programmatic sanitizer intercepts all run_command calls on swarm terminals, stripping unauthorized flags (-p, --sandbox, --print) and enforcing the exact approved command for each agent. Claude gets --dangerously-skip-permissions, Codex gets -a never -s workspace-write with its update check off — no permission dialogs or update offers during swarms. A new folder's trust prompt is answered for you only when you've connected your agents and it is a project folder (never your home folder); otherwise you answer it once.
Interactive Agent Mode — all agents (including Gemini CLI) launch in interactive mode so they retain full tool access, including file writing and command execution.
Token Budget Estimates — shows per-agent estimated tokens and cost before you launch, so you know what the swarm will cost
Swarm Dashboard — Ctrl+Shift+S opens a real-time view with two tabs: Tasks (kanban: Pending · In Progress · Completed · Failed) and Messages (chronological log). Also accessible by clicking the "Swarm Active" indicator in the bottom status bar.
Clear Confirmation — clearing a swarm requires explicit confirmation to prevent accidental loss of in-progress work
Agent Install Status — the AI Agents sidebar shows green checkmarks for installed agents and red X icons for missing ones. Clicking a missing agent's icon shows installation instructions.
Message Bus — agents communicate through a shared message queue with typed messages (task, result, question, info, review)
Task Queue — create tasks, assign to agents, track status across Pending → In Progress → Completed
MCP-native end to end — Claude Code, Codex, and Gemini CLI all speak MCP. A task is completed only by swarm_update_task or the Tasks tab's Done/Fail, never by what an agent prints; for Codex and Gemini workers Termpolis also passes what their screens show to the conductor, as hints.
6 swarm MCP tools — swarm_send_message, swarm_read_messages, swarm_create_task, swarm_list_tasks, swarm_update_task, swarm_list_agents