# archeus — full text > The memory and workspace layer for AI coding agents. Every page of https://claudectl.space, the FAQ and the release history, concatenated as plain text for language models. Version: 2.8.1 · Content as of: 2026-09-01 · Canonical HTML: https://claudectl.space · Documentation: https://docs.claudectl.space --- # The memory and workspace layer for AI coding agents. Persistent per-project memory, every session you have ever had, and control over what the next one costs. Works with Claude Code, OpenAI Codex and pi. ## The memory and workspace layer for AI coding agents. Persistent per-project memory, every session you have ever had, and control over what the next one costs. Works with Claude Code, OpenAI Codex and pi — a terminal UI and a desktop GUI over the same engine. install: ``` pipx install archeus ``` - Python 3.10 or newer, and the Claude Code CLI you already have. - No API key — it uses your existing Claude Code authentication. - Zero runtime dependencies: pure Python standard library. - Windows, macOS and Linux, tested in CI on all three. ## Excellent inside a session. Forgetful between them. Every new session starts from nothing, old sessions are hard to find, and the only way to give the agent context is a CLAUDE.md that grows until it costs more than it is worth. That is not one vendor’s bug — it is what every coding agent that runs in a terminal has in common. Claude Code stores each session as a JSONL transcript under your config directory and gives you no way to browse them. /resume reattaches you to something recent in the current directory; it does not search, does not span projects or accounts, and cannot answer "what did I do in this repo three weeks ago, and what did we decide". The context problem is worse, because it is recurring. CLAUDE.md rides in the model context on every single message, so its size is a permanent per-turn tax. The usual failure is not that the file is wrong — it is that it is too big to justify and too tedious to prune. A big project either starves the agent or floods it. archeus sits in front of the agent and fixes both. Pick a project, see every session you have ever had in it, and launch with the model, effort, permissions and context you meant. Both halves of where this is going have started. A session no longer has to talk to Anthropic: point it at a local model server, at OpenRouter, at OmniRoute’s free tier or at anything that serves POST /v1/messages, and it stays a real Claude Code session with its agents, skills, hooks and MCP servers intact. And it no longer has to be Claude Code at all — OpenAI Codex and pi are read end to end, their sessions and spend merge into the same lists, and one memory graph is delivered to all three. Claude Code is the first surface, not the boundary, and it is no longer the only one; a harness of archeus’ own remains a long-term goal rather than a feature. ## Intelligent memory, not a memory dump A semantic graph of your codebase, injected through three token-budgeted surfaces. The always-on cost is a bounded index that does not grow as the codebase does. | Surface | What Claude sees | Cost | |---|---|---| | CLAUDE.md micro-index | repo one-liners, module names, a recall pointer | ≤250 tokens, every session | | .claude/rules/ per module | entities and relations, path-scoped | 0 until Claude touches those files | | UserPromptSubmit hook (opt-in) | the subgraph relevant to the prompt you just typed | ≤600 tokens per prompt, under a second, local | - Bounded and self-consolidating: Duplicate entities merge across modules and a global importance cap evicts the least-connected, so the always-on cost stays flat while accuracy rises. The memory gets leaner and sharper the more you build, not heavier. - Nothing shrinks without a way back: Pin an entity and the cap can never evict it. Fence a region of CLAUDE.md and AI compression never even sends it to the model. Prune names what it will drop before it drops it. Every replacement is snapshotted and restorable, and restoring is itself snapshotted. - Temporal facts: When you migrate Flask to FastAPI the old fact is invalidated with a timestamp rather than deleted — kept as history, never injected. Memory tracks what is true now and what changed, instead of drifting stale. - It learns from every session: Durable lessons — error to fix pairs, decisions, preferences — are distilled from transcripts, reviewed, injected when relevant and decayed when unused. Conventions that recur across your repos are promoted to your user-level CLAUDE.md, so a rule learned once is remembered everywhere. - Local, deterministic retrieval: Four rankers combined by Reciprocal Rank Fusion, which uses each ranker’s position rather than its score, so nothing needs calibrating. No embeddings, under half a second on 500 entities. The honest limit: this is lexical retrieval — a query sharing no vocabulary with the stored summary will miss. Graph memory is inspired by cognee and retrieval budgeting by Aider’s repo-map; both were reimplemented from scratch in pure stdlib. ## A workspace, not a pile of chats Every project Claude Code has ever opened, every session inside it, sorted by recency and searchable across the lot — across every account at once. - Sessions: Browse, search, tag, fork, rename, resume, archive and export. Read any transcript in a pager with full-text search inside the conversation, and list the files a session actually changed, derived from its tool calls. - Usage: Tokens in, out and cached, plus estimated cost per project, per session, per day and per model — parsed from your own local transcripts, not from an API. Daily limit bars with reset times on the main screen. - Multiple accounts: Claude Code picks its account through CLAUDE_CONFIG_DIR. archeus detects every configured account, merges their projects into one list, shows per-account usage side by side, and sets the variable for you at launch. - Launch control: Model, reasoning effort, permission mode, agent, extra PATH entries and additional context roots — chosen before each launch and remembered per project. Start a named session, or one in a fresh git worktree. - Context hand-off: Hand off any session to a new one, under any account, from the button on its row — for when the context window fills up, or an account hits its limit mid-task. The new chat starts with the old transcript on disk and is told to read it first. ## Where the tokens go CLAUDE.md and memory files ride in the context on every message. archeus makes that cost visible, then cuts it. - Context weight audit: One screen estimating the tokens auto-loaded on every turn for this project: CLAUDE.md broken into its blocks, the global CLAUDE.md, rules files marked lazy when they are glob-scoped, the system prompt, SessionStart hook injections and MCP servers — with a running always-on total and inline warnings. - Prune and compress: The session-topics log is capped to the most recent entries and rebuilt in place. AI compression rewrites the hand-written part into a lean lookup-table style, shows a before-and-after token count and a diff to approve, and preserves the machine-maintained blocks verbatim. - Deny heavy reads: Scans the project and writes permissions.deny rules for node_modules, dist and lockfiles into the project settings, so a stray read cannot pull thousands of tokens of generated content into context. - Cheaper models for the grunt work: archeus’s own internal calls default to a cheap economy model. Plan → Execute runs the expensive model once for the plan and a cheap — or, through OmniRoute, a free — one for execution. - Measured, not asserted: Rule files were being loaded on every turn because they carried Cursor’s frontmatter key rather than Claude Code’s. Fixing it took this repository from 22,238 always-on tokens to 18,363 — 3,875 off every single turn. ## Get archeus The memory and workspace layer for AI coding agents. Five ways in, all free, MIT licensed and one line each — and all of them need at least one coding CLI already installed: Claude Code, OpenAI Codex or pi. pipx — recommended: ``` pipx install archeus archeus ``` pip: ``` pip install archeus ``` npx — nothing to install first: ``` npx archeus ``` Claude Code plugin: ``` /plugin marketplace add babarmuhammad/archeus /plugin install archeus@archeus ``` Or clone the repository and run python claude-sessions.py. There is nothing to build and nothing to install alongside it. --- # Features Everything archeus does, grouped. It is one engine behind two interfaces: a keyboard-first terminal UI and a desktop GUI with full parity. ## Sessions & search Claude Code’s flat pile of chats, turned into a browsable workspace. - Session browser — every project Claude Code has ever opened and every session inside it, sorted by recency. - Search — filter live as you type, or search the content of every session across every project and resume the hit. - Transcript viewer and export — read any session in a pager with in-conversation search and a message-position counter; export to markdown. - Rename, fork, continue, tag and archive, with archive being a restorable folder rather than a delete. - Changed files — the files a session created or edited, derived from its tool calls. - Session info — tokens, estimated cost, models used, git branch and duration. - Quick-resume shortcuts on the main screen jump straight back into recent sessions across all projects. ## Project memory The feature nothing else does: task-scoped, token-budgeted memory injection at the launcher. - A semantic graph of the codebase built by summarising every repo and module incrementally, merged with the real dependency graph from the connections engine. - Three injection surfaces with zero duplication: a ≤250-token always-on index, path-scoped rules files that cost nothing until Claude opens a matching file, and an optional per-prompt hook budgeted to ≤600 tokens. - Auto-refresh that converges — each cycle extracts at most a configured number of modules and queues the rest; work is never marked done unless it was done. - Lessons distilled from every session, human-reviewed, injected when relevant, decayed when stale. - Cross-project conventions promoted into your user-level CLAUDE.md. - Ask the project — grounded question answering over the graph, with only the relevant subgraph as context. - Recent-work memory — a token-free one-line summary per session, injected as a digest at the next SessionStart. ## Architecture graph A self-contained interactive HTML view of your project’s real dependency structure. It opens at the repository level and expands down to single files, across Python, C, C++, C#, JavaScript and TypeScript. Nothing is uploaded and nothing is served — it is one HTML file written into the project, opened straight from the session browser. ## Plan → Execute Plan on an accurate model, execute on a cheap or free one. A headless planning pass runs on the strong model. You approve or edit the plan, and the approved plan is handed to a cheaper model to carry out. Free execution routes the execute half through OmniRoute’s free tier while planning stays on your real account. ## Any model, still a real session Point a session at a local server, OpenRouter, OmniRoute or anything that serves POST /v1/messages. archeus could always aim a real Claude Code session at another backend — that is what the OmniRoute support has always been — but the capability was wired to one product name. Settings → Model provider now takes any endpoint, with a translating gateway for backends that speak the OpenAI chat format instead, and a failover list so a dead model is retried rather than hung on. The session stays a session. Agents, skills, hooks, MCP servers, slash commands and checkpoints all keep working, because none of them ever talk to the model API. What a backend swap genuinely costs is written down rather than glossed: subagents, prompt caching, extended thinking and web_search are affected, and three of those four cannot be fixed from outside Claude Code. archeus’ own Claude calls — memory extraction, lesson distillation, code review, the CLAUDE.md, agent, skill, hook and system-prompt generators — can be routed there too. They are the cheapest and highest-volume calls it makes and the best fit for a local model. It is opt-in and off by default: they run unattended, and moving them changes which account is billed. ## Three coding CLIs, one workspace Claude Code, OpenAI Codex and pi — their sessions, projects and spend in the same lists, and one memory graph behind all three. A provider is an endpoint; a harness is the binary itself, plus the conventions it keeps on disk — where its transcripts live, what its instructions file is called, what a session id even is. The two are orthogonal, and archeus now reads three harnesses with nothing to configure: it looks for each CLI’s binary and its home, and one it cannot find is simply not offered. | CLI | Home | Instructions file | |---|---|---| | Claude Code | ~/.claude, and every other account | CLAUDE.md | | OpenAI Codex | ~/.codex | AGENTS.md | | pi | ~/.pi/agent | AGENTS.md | - One memory graph, not three: A project’s memory is the project’s, not one tool’s memory of it, so the same digest is delivered into every instructions file an installed CLI reads — and a machine with no Codex never grows an AGENTS.md. The agent routing table deliberately does not follow: delegating to a subagent is a Claude Code capability, and writing “delegate with the Agent tool” into AGENTS.md would instruct Codex to use a tool it does not have. - Skills install everywhere: SKILL.md is the Agent Skills standard and all three read the same file, so a personal skill lands in every account and every CLI. A project skill writes .claude/skills and .agents/skills — two directories, not three, because .agents/skills is the cross-harness convention Codex and pi share. - The launch picker asks which tool first: A tab per installed CLI and per configured backend, because the answer decides what the rest of the form means: a Codex session has no worktree and no name-at-launch, a pi session has no permission mode. Those fields are removed with the reason stated rather than left as dead inputs, and each CLI brings its own model list and its own effort scale — --effort ultracode is Claude Code’s and Codex rejects it. - Nothing is guessed, and nothing is written: A Codex thread’s transcript is named by its index, so archeus reads the path out of the index rather than reconstructing it; a pi session directory’s name is lossy, so archeus reads the working directory out of the file instead of decoding the name. Both stores are opened read-only. - And any of them can be switched off: Off means everywhere — the launch tabs, the project list, the sessions list, the usage table, and the instructions files that get a memory block. Nothing is uninstalled and nothing on disk is touched. Claude Code remains much the deepest of the three: hooks, MCP servers, subagents, plugins, output styles and checkpoints are its alone. Every one of those screens says which CLI cannot do it and why, rather than hiding the gap or failing at launch. ## Claude Code integration - Multiple accounts: Two or more accounts side by side, one row per project, cross-account hand-off and account-accurate memory. Session lists — live and archived — span every account, and an action puts a session back in the one it came from. - MCP servers: Add, remove and inspect servers, see which are actually connected to a project, and run an analysis that documents a server’s real tools into a re-updatable block in your CLAUDE.md. - Agents & skills: A category-organised agent library mirroring a community catalog of 150+ agents in 10 categories, per-project subagent selection, adaptive suggestions from local signals, and a skills manager. - Hooks: Thirty-one ready-made Claude Code hook templates — formatting, safety guardrails, failure logging, notifications, memory freshness, context injection, token savers — plus AI-generated ones. Installing, disabling and removing all apply across every account. - Status line, failover & checkpoints: A cheap per-turn status line, a proxy that retries a dead model instead of hanging, and a strictly read-only view of Claude Code’s checkpoint store. - Plugins, marketplaces & output styles: Browse and install from plugin marketplaces, see what a plugin actually contains before it runs, and switch output styles — every mutation goes through the claude CLI rather than editing its caches, whose format has already changed once. - The client itself: Which Claude Code version each account is on, what its state directory is costing you on disk, which background agents are running, and a typed editor for its own settings — read and written the same way Claude Code reads them, never rewritten wholesale. - Repos & worktrees: Every repository under a project root, including submodules and linked worktrees, with branch, dirty state and ahead/behind read from disk rather than from a subprocess — so opening the board costs nothing. Launching a session into a fresh worktree is one option on the launch screen. - Scheduled loops: A session-scoped /loop, and headless loops registered with the OS scheduler that wake up, run a prompt against a project and write what they found into a journal. Both are listed, both can be stopped, and neither is a daemon archeus keeps alive. - A log of what it did: Every Claude call archeus makes on your behalf, every background job, every scheduler run and every proxy failure, with what failed and why. The point of a tool that acts unattended is being able to read back what it did. ## Project health & auto-fixes - Pre-launch health checks. - Context-loss insurance after /compact — memory lives outside the transcript, so it is re-injected at the next launch regardless of what the conversation dropped. - A permission-fatigue killer and a token-burn advisor. - Daily usage tracking with an optional threshold badge. - Code review of your working diff against your CLAUDE.md rules and learned lessons, reporting confidence-scored findings. ## The desktop GUI The whole thing as a desktop app, with full parity to the terminal UI. A native window when PyQt6 is present, otherwise your browser, served over loopback only. 32 palettes, 8 skins and 4 themed worlds — a skin changes the shape of the app, not just its colours. ## Compared with the alternatives | | Bare Claude Code | Hand-maintained CLAUDE.md | archeus | |---|---|---|---| | Browse past sessions | /resume, current directory, recent only | No | Every session, project and account | | Search session content | No | No | Yes | | Tag / fork / archive | No | No | Yes | | Project context | You write it | You write and prune it | Maintained automatically, budgeted | | Cost as the project grows | Grows with your file | Grows with your file | Bounded index plus on-demand detail | | Multiple accounts | Set CLAUDE_CONFIG_DIR yourself | — | Detected, merged, picked at launch | | Dependency graph | No | No | Interactive graph | | Extra runtime dependencies | — | — | None, stdlib only | ## Scriptable, not only interactive The parts worth calling from a hook, a CI job or another agent are commands. commands: ``` archeus recall "" # the task-relevant slice of this project’s memory archeus workspace status # what this project is, in one screenful archeus review --staged # review the diff against CLAUDE.md and the lessons archeus statusline # one line per turn, for Claude Code’s status line archeus sync-accounts # level every account up to what you have provisioned ``` It also ships as a Claude Code plugin, so recall, review and workspace status are available as slash commands without leaving the session. The plugin deliberately installs no hooks: archeus already places those per account, and one settings entry with two owners is how an uninstall leaves the other looking broken. ## What archeus does not do Stated plainly, because a feature page that only lists strengths is not useful. - It is not a Claude Code replacement. It configures and launches Claude Code; every actual coding turn is Claude Code doing the work. - It is Windows-first. macOS and Linux are supported and tested in CI, but Windows gets the widest version matrix and by far the most real-world use. - It does not host a model of its own, and it does not bill you. By default a session runs on your existing Claude Code authentication and your existing quota. If you route one elsewhere, archeus runs a loopback proxy to translate and retry — your endpoint, your key, your bill. - The memory features cost tokens to build. Extraction and lesson distillation are Claude calls, routed to a cheap model and run rarely, but not free. The saving is on the per-message context you stop paying for. - It is a young project. Small user base, and the API surface still moves. - If your CLAUDE.md is 40 lines and stays that way, you do not need this. --- # Download archeus archeus is a pure-Python package with no runtime dependencies. Any of these takes one line, and none of them needs an API key — it uses the Claude Code authentication you already have. ## pipx — recommended Installs into its own environment and puts one command on your PATH. ``` pipx install archeus archeus ``` Upgrade later with pipx upgrade archeus, or from the Updates screen inside archeus. ## pip ``` pip install archeus archeus ``` Use a virtual environment if you would rather not install into your system Python. The package installs one console script, archeus. ## npx — without installing it first You already have npm if you installed Claude Code with it. ``` npx archeus ``` The npm package is a launcher rather than a copy of the tool: it runs archeus if it can find it, and if it cannot, it prints the exact command and hands the install to pipx — or to pip, inside a virtualenv or when pipx is missing. On Windows it also covers the case where pip has installed archeus but its Scripts directory is not on your PATH: it runs it as a module and tells you which directory to add. ## Claude Code plugin If you would rather stay inside the session. ``` /plugin marketplace add babarmuhammad/archeus /plugin install archeus@archeus ``` The plugin ships commands and skills. It deliberately ships no hooks: archeus’s own hook manager already places the recall, worklog and guard hooks per account, and giving one settings.json entry two owners means installing both runs the same hook twice. ## From a git checkout ``` git clone https://github.com/babarmuhammad/archeus.git cd archeus python claude-sessions.py # terminal UI python claude-sessions.py --gui # desktop GUI ``` There is nothing to build. A checkout is told to git pull when it updates itself, rather than having a release installed over it. ## What a release contains - The archeus package: the terminal UI, the desktop GUI, the memory engine, the connections engine and the hook, agent and skill managers. - Bundled starter skills and 31 hook templates. - The Claude Code plugin manifest, with commands and skills generated from the package so they cannot fall behind it. - A signed source distribution and a wheel on PyPI. Versioning is semantic; the changelog follows Keep a Changelog. - The npm launcher, and the name on RubyGems, crates.io and NuGet — those three are pointers that print the pipx line rather than installing anything, so the name resolves to this project wherever people look for it. ## Requirements | | | |---|---| | Python | 3.10 or newer | | Claude Code | The CLI, auto-detected on PATH or at ~/.local/bin/ | | Runtime dependencies | None — Python standard library only | | Optional | PyQt6, for the native desktop window. Without it the GUI opens in your browser over loopback. | | Platforms | Windows, macOS and Linux; CI tests all three | | Licence | MIT | --- # Architecture archeus is one engine with two front ends. Everything below runs locally, on the standard library, against files Claude Code already writes. ## The architecture graph Every module and its dependencies, expandable down to single files — across Python, C, C++, C# and JavaScript or TypeScript. The connections engine parses your source, resolves imports into real cross-module edges and ranks each node by importance. The result is written as a single self-contained HTML file inside the project and opened from the session browser. Nothing is uploaded, and nothing is served. The same graph is what the memory layer merges its summaries into, which is why the memory of a module knows what that module depends on. ## The layers - Store and transcripts: One reader per file format, and the reader streams. Transcripts reach a hundred megabytes, so they are iterated rather than read, with an optional substring prefilter tested against the raw line so a caller that wants only the tool calls never pays for a parse it discards. - Memory: A semantic graph in the project, consolidated under an importance cap, with superseded facts invalidated rather than deleted. An absent file is an empty state; a file that exists and will not parse is a fault, moved aside and reported rather than silently returned as empty. - Jobs: Long Claude calls run inline as a banner that survives navigation, and escalate to a modal only when the job actually parks at an approval gate — decided by what the job did, not by a per-kind allowlist that would go stale. - The local HTTP API: The GUI is a single-page app over a loopback server. A custom request header is not an auth boundary and neither is loopback, so the guard is a Host allowlist, a rejection of browser fetch metadata, and a per-run secret compared in constant time. - Claude Code’s own state: settings.json and the plugin caches are read-modify-written atomically, never rewritten. archeus shares those files with Claude Code, so a half-written one breaks your session, not just this tool. ## Principles the code is held to - Zero runtime dependencies. Everything is the Python standard library, so there is nothing to resolve, pin or break. - Read Claude Code’s state, never build on its naming. Where the documentation and the disk disagree, the disk wins and the unknown is reported as unknown rather than guessed. - Read-only where it matters. The checkpoint store is never written; restoring stays Claude Code’s job. - A verification tool that runs nothing reports success, so every gate has a floor on how much it did and every one has been mutation-verified against the bug it exists to catch. - The interface animates transform and opacity only. Anything that forces a GPU readback tears the compositor. --- # Community archeus is a young project with a small user base. Bug reports are genuinely useful, especially from macOS and Linux, where there is less real-world use than on Windows. ## Getting help - Something is broken: Open an issue. Include your platform, Python version, the archeus version and what you ran. If a screen faulted, the traceback matters more than the description. - A question, or an idea: Start a discussion. Ideas that begin as a discussion tend to arrive as better issues. - It behaves oddly rather than failing: The documentation site has a troubleshooting page covering the common causes — a Claude Code CLI that is not on PATH, an account whose config directory moved, and a GUI that opens in the browser because PyQt6 is not installed. ## Contributing Pull requests are welcome. The test suite is the contract: it is large, it is fast, and every gate in it exists because something broke once. If you change behaviour, the test that would have caught the old bug should fail before your fix and pass after it. ## Code of conduct The project follows the Contributor Covenant. It applies to the issue tracker, discussions and pull requests, and the full text is on this site. --- # About archeus archeus is built and maintained by Babar Muhammad Anas. It is MIT licensed, free, and has no subscription and no API key of its own. ## Why it exists It started as a session picker and became a workspace layer, because every problem it fixed turned out to sit one level below the one before it. Browsing sessions needs a project list; a project list is only useful if the next session starts with the right context; context is only affordable if something bounds it. The memory graph is the end of that chain, not the beginning. ## Credits & inspiration archeus is built on ideas from the wider Claude Code ecosystem. With thanks: - cognee and Aider’s repo-map: Graph memory and retrieval budgeting, both reimplemented from scratch in pure stdlib for project memory. - Anthropic’s claude-code code-review plugin: The confidence-scoring, high-threshold, CLAUDE.md-compliance review pattern behind archeus review. - thedotmack/claude-mem: The session-observation to summary to SessionStart injection pattern behind recent-work memory. - anthropics/claude-plugins-official: The Skills extension model and plugin structure. - diegosouzapw/OmniRoute: A self-hosted free-tier model proxy; the inspiration for economy model routing and now the backend behind free execution in Plan → Execute. - olsenbrands/fable-foreman: The skill plus worker and verifier subagent pattern for delegating execution to cheaper models under a frontier model’s plan. - VoltAgent/awesome-claude-code-subagents: The community catalog the agent library mirrors — 150+ agents in 10 categories. All credit for the agents goes to their original authors. - microsoft/markitdown: Document-to-markdown token-efficiency thinking; document ingestion is on the roadmap. - claudemarketplaces.com and Ponytail: Skill and plugin discovery, and the code-minimization rule behind one of the bundled hooks. Bundled starter skills are original write-ups inspired by patterns in community collections, each credited in-file. ## Licence MIT. Use it, fork it, ship it. archeus uses your existing Claude Code authentication and adds no subscription and no API key of its own. --- # Contributing to archeus archeus has no build step and no runtime dependencies, so getting from a clone to a running copy is one command. ## Setting up ``` git clone https://github.com/babarmuhammad/archeus.git cd archeus python claude-sessions.py # terminal UI python claude-sessions.py --gui # desktop GUI python -m pytest # the suite ``` Python 3.10 or newer. pytest is the only development dependency; PyQt6 is optional and only affects whether the GUI opens in a native window or your browser. ## The test suite is the contract - Every gate exists because something broke once. If you change behaviour, write the test that would have caught the old bug: it should fail before your fix and pass after it. - Mutation-verify a new gate. A test nobody has watched fail is not a gate — revert your fix and confirm it goes red. - A tool whose only output is "it passed" needs a floor on how much it did. The smoke checks count what they ran and fail below it, because one that ran nothing once reported success for three runs. ## Style and the lint gate The ruff rule set is deliberately narrow: the bug-finding rules only. Enabling the full style set would land the gate red and get it switched off. Style changes are welcome as their own mechanical diff, not bundled into a behaviour change. - Zero runtime dependencies is a hard constraint, not a preference. A new import from outside the standard library will not be merged. - Commits follow Conventional Commits, and the changelog follows Keep a Changelog. - Anything writing to stdout from a hook or the status line must reconfigure the stream to UTF-8 — Claude Code captures stdout as a pipe, so CPython otherwise picks the locale codepage. ## Opening a pull request - Describe the behaviour that changed and the test that proves it. - Keep the diff to one concern. A refactor and a fix in one branch is two reviews in one. - CI runs on Windows, macOS and Linux across the supported Python versions on every push. It must be green. --- # Support archeus A donation here is a thank-you, not a purchase, and the things that help most cost nothing at all. ## The tip jar Donations go through Ko-fi. There is no widget on this page and no Ko-fi script runs here — the link below is an ordinary hyperlink, and nothing of theirs loads until you follow it. They buy nothing, and that is the design rather than an oversight. There is no tier, no perk, no member role, no private channel, no early access and no name in a credits file. Nothing about the software, the roadmap or the answer you get on an issue changes because you did or did not give. The moment a donation buys something it stops being a gift and becomes a consumer supply contract, with a right of withdrawal and a real refund policy behind it. Keeping it a gift is what keeps this page three paragraphs long. - [The project on Ko-fi](https://ko-fi.com/babarmuhammad) — One-off or monthly, any amount. - [Donations and refunds](/legal/refunds) — What a donation does and does not entitle you to, and what happens to one made by mistake. ## What it does not change - The licence stays MIT, for everybody, with no sponsor-only build and no feature held back behind a tier. - There is no telemetry to switch off and no account to make: archeus runs entirely on your machine, against the CLI you already have. - There are no runtime dependencies to pay for. The cost of this project is time, not infrastructure. ## Ways to help that cost nothing Money is the least useful thing you can send. A project nobody can find is a project nobody uses, and a bug nobody reports is a bug that stays. - Report what broke. A transcript, the version and what you expected beats a star. - Say what is missing. The sharpest features here started as somebody describing a workflow that did not fit. - Tell somebody. The name is one vowel from a Pokémon and shares a crates.io entry with an unrelated Rust tool, so a post that names what this one actually does is worth more than it sounds. - Send a pull request. The setup is one clone and one command, and the test suite says what a change owes. - [Report a bug](https://github.com/babarmuhammad/archeus/issues/new) - [Discussions](https://github.com/babarmuhammad/archeus/discussions) - [Contributing](/contributing) --- # Privacy policy These websites collect nothing from you. There is no form, no account, no newsletter and no analytics. What follows is mostly about the one thing that does happen anyway — a web server writing down that a request arrived. ## Who is responsible Babar Muhammad Anas, an individual established in Italy, operates https://claudectl.space and https://docs.claudectl.space and is the data controller for them. - [babarh174@gmail.com](mailto:babarh174@gmail.com) — the contact route for anything on this page There is no company behind this, no marketing department and no data team. It is one person publishing a free, MIT-licensed tool. ## What the sites collect: nothing Not "nothing much" — nothing. This is a static site and it has nowhere to put anything you typed. - No form, input field, contact form, newsletter, waitlist or comment box exists on either site. - No account, no login, no profile. - No analytics, no tag manager, no pixel, no session recorder, no A/B tool, no error reporter. - No advertising, no profiling, no automated decision-making, and nothing is sold or shared with a data broker. - The two "copy" buttons run entirely in your browser and send nothing anywhere. The pages are built ahead of time and served as files. Fonts come from your own operating system, and every image is served from the same domain as the page, so simply reading the site contacts nobody else. ## What the hosts log A request has to reach a server, and a server writes down that it happened. This is the only personal data processed in connection with these sites. https://claudectl.space is served by Vercel. https://docs.claudectl.space is served by Vercel and by GitHub Pages from the same build. Like any web host, they record request data — typically the IP address, the user agent your browser sends, the page requested and the time — to deliver the page, keep the service available and defend it against abuse. That processing rests on legitimate interests under Article 6(1)(f) GDPR: a website cannot be delivered or protected without it. The logs are the hosts’, not this project’s — they are kept and deleted on the hosts’ own schedules, and are not used here to build any profile of a visitor. Because those providers operate globally, serving a page can involve a transfer outside the EEA under the safeguards described in their notices. - [Vercel privacy notice](https://vercel.com/legal/privacy-policy) - [GitHub privacy statement](https://docs.github.com/en/site-policy/privacy-policies/github-privacy-statement) ## Cookies There are none, which is why there is no consent banner. Neither site sets a cookie or writes anything to your device — no cookie, no local storage, no session storage, no database in the browser. The cookie policy says so at more length, and says what would have to change for that to stop being true. - [Cookie policy](/legal/cookies) ## Donations If you choose to support the project, you do that on Ko-fi, and the payment is taken there rather than here. The Support link is an ordinary hyperlink. There is no Ko-fi widget, button script or iframe on these sites, so nothing about Ko-fi loads or runs until you click through. Once you do, you are on Ko-fi’s website and their privacy policy and terms govern what happens there. Your card details go to their payment provider and never reach this project. What a recipient can see about a supporter is decided by Ko-fi, not here. Read their notice for the current answer rather than taking a description of it on trust. - [Ko-fi privacy policy](https://more.ko-fi.com/privacy) - [Ko-fi terms](https://more.ko-fi.com/tos) - [The project on Ko-fi](https://ko-fi.com/babarmuhammad) Donations are voluntary. They buy nothing: there is no tier, no perk, no member role, no private channel, no early access and no name in a credits file, and nothing about the software or the support you receive changes because you did or did not give. ## Where this project is published, and what that costs you The sites load nothing from anyone else. The README does, and it is worth being explicit about it, because most people meet this project on GitHub or PyPI rather than here. - The README’s status badges are images fetched from img.shields.io, and its screenshots are fetched from raw.githubusercontent.com. Whenever a page renders that README — on GitHub, on PyPI, or in another tool — your browser requests those images, and that request carries your IP address and user agent to those third parties. - Following any outbound link — GitHub, PyPI, npm, RubyGems, Ko-fi, the documentation of another project — takes you to somebody else’s site, under their policies. - The software itself is separate from these websites: it runs on your machine, and what it does with your data is described in the manual, not here. ## Your rights Under the GDPR you can ask for access to your personal data, and for it to be corrected, erased or restricted; you can object to processing based on legitimate interests; and you can ask for a copy in a portable form. In practice there is very little to ask about, because this project holds nothing: no list, no database, no mailing list, no visitor record. Requests about the hosts’ own server logs are best made to them, and the notices linked above say how. Anything else, write to babarh174@gmail.com and you will get an answer. If you are not satisfied you can complain to a supervisory authority — in Italy that is the Garante per la protezione dei dati personali, and you may also complain to the authority where you live or work. - [Garante per la protezione dei dati personali](https://www.garanteprivacy.it/) - [Email babarh174@gmail.com](mailto:babarh174@gmail.com) ## Changes to this policy This policy is effective from 16 September 2026. If the sites ever start collecting something — an analytics script, a form, a Ko-fi widget — this page changes before that ships, and its effective date changes with it. The history of every change is public in the repository. - [This page in the repository](https://github.com/babarmuhammad/archeus/blob/main/www/lib/legal.ts) --- # Terms archeus is free software published by one person. These terms cover the two websites and set out what the software is and is not promised to do. ## Who you are dealing with Babar Muhammad Anas, an individual established in Italy, publishes the software and operates https://claudectl.space and https://docs.claudectl.space. - [babarh174@gmail.com](mailto:babarh174@gmail.com) ## The software archeus is licensed under the MIT licence. You may use, copy, modify and redistribute it on those terms, and the licence text is the agreement — this page does not add to it. The MIT licence provides the software "as is", without warranty of any kind, and its authors are not liable for any claim or damage arising from it. That applies whether you paid nothing, which is the normal case, or chose to donate. It is a young project and the interfaces still move. It reads and writes files belonging to other tools on your machine, and the manual says which; read that before pointing it at work you cannot afford to lose, and keep backups you would have kept anyway. - [The MIT licence](https://github.com/babarmuhammad/archeus/blob/main/LICENSE) - [The manual](https://docs.claudectl.space) ## The websites - Everything on these sites is published for information. It is written as accurately as it can be and it is not a promise about anything. - Pages change, move and are rewritten without notice. Nothing here guarantees the sites are available, complete or up to date. - The sites collect nothing from you. There is no account to hold, no content to submit and no order to place. ## Donations A donation is a gift. It is not a purchase, and it does not buy a product, a service or a promise. Donations are voluntary. They buy nothing: there is no tier, no perk, no member role, no private channel, no early access and no name in a credits file, and nothing about the software or the support you receive changes because you did or did not give. Donations are collected through Ko-fi and handled by its payment provider; your card details never reach this project. Giving does not change the licence the software is under, does not create a support contract, does not grant priority on issues or feature requests, and does not extend or alter the warranty disclaimer above, which remains exactly as it is. - [Refund policy](/legal/refunds) - [The project on Ko-fi](https://ko-fi.com/babarmuhammad) ## Names and artwork archeus is not affiliated with, endorsed by, or supported by Anthropic. Claude and Claude Code are Anthropic’s trademarks, and OpenAI Codex, pi and every other product named on these sites belong to their own owners. They are named to say what the software works with, which is a statement of fact, not a claim of association. The archeus mark and the banner lockup were designed by Federico Coscia and are credited in the documentation. They are artwork, not source code, and the MIT licence on this repository covers the software. - [Credits](https://docs.claudectl.space/credits/) ## Liability and law To the fullest extent the law allows, no liability is accepted for any loss arising from using these sites or the software. Nothing here limits liability that cannot lawfully be limited — including for death or personal injury caused by negligence, or for fraud — and if you are a consumer, nothing here takes away the rights your own country’s law gives you. These terms are governed by Italian law. If you are a consumer resident elsewhere in the EU, you keep the protection of the mandatory rules of your own country. ## Changes Effective from 16 September 2026. These terms change when the project does, and every version is in the repository’s history. --- # Cookie policy Short version: there are no cookies, so there is nothing to consent to and no banner asking you to. ## This site stores nothing on your device Not a necessary cookie, not an analytics cookie, not a preference cookie. Nothing. - No cookie is set by either site. - Nothing is written to local storage, session storage or an in-browser database. - There is no analytics, no tag manager and no third-party script of any kind, so nothing else gets the chance to set one either. - There is no login and no preference to remember, because there is nothing to log in to and nothing to configure. ## Why there is no consent banner Consent is required for storing information on your device or reading information already there. Nothing here does either. The rule people mean when they say "cookie law" — Article 5(3) of the ePrivacy Directive, in Italy Article 122 of the Codice in materia di protezione dei dati personali — attaches to the act of storing or accessing information on a user’s device. This site does neither, so there is no consent to collect. A banner asking you to agree to nothing is not caution, it is noise, and it trains people to click through the ones that matter. If that changes, the banner arrives with the thing that made it necessary. ## What still happens anyway - Your browser caches pages and images, as it does for every site. That is your browser’s own storage, under your control, and nothing here reads it. - The web hosts log that a request arrived. That is a record on their servers, not storage on your device, and the privacy policy covers it. - Other sites you reach from here — GitHub, PyPI, npm, RubyGems, Ko-fi — set their own cookies once you are on them. A link to a site is not an embed of it: nothing of theirs runs until you click. - [Privacy policy](/legal/privacy) ## If this changes Effective from 16 September 2026. Adding analytics, an embedded Ko-fi widget, a comment system or anything that remembers you between visits would re-open this question, and this page and its date would change first. --- # Refund policy Nothing is sold here. The only way money moves is a voluntary donation on Ko-fi, and this page says plainly what that does and does not entitle you to. ## A donation is a gift, not a purchase Nothing is supplied in return for it, which is the whole reason it stays simple. Donations are voluntary. They buy nothing: there is no tier, no perk, no member role, no private channel, no early access and no name in a credits file, and nothing about the software or the support you receive changes because you did or did not give. Because no goods or services are supplied in exchange, a donation is not a consumer contract and the statutory right of withdrawal that comes with buying something online does not attach to it. That is not a term being imposed on you — it follows from there being nothing bought. ## Refunds - As a matter of policy no refunds are offered, because nothing was sold. A gift given freely is not an order that can be cancelled. - A donation made by mistake is a different thing, and it is not treated as final. Write to the address below, or raise it with Ko-fi, and it will be sorted out where the payment provider still allows it. - Ask quickly. Whether a payment can be reversed at all is decided by Ko-fi and the card or PayPal rules behind it, and every one of those is a clock. - This project never sees or holds your card details; they stay with the payment provider. - [Email babarh174@gmail.com](mailto:babarh174@gmail.com) - [Ko-fi terms](https://more.ko-fi.com/tos) - [Ko-fi help centre](https://help.ko-fi.com/) ## If something went wrong Write to babarh174@gmail.com. A real person reads that mailbox. A donation made by accident, twice over, or for far more than you meant is worth writing about rather than shrugging at — say what happened and roughly when, and it will either be put right or you will be told plainly why it cannot be. - [Email babarh174@gmail.com](mailto:babarh174@gmail.com) ## What would change this If the project ever offered something in return for money — a membership tier, a perk, early access, priority support, anything at all — that would stop being a gift and become a sale. A sale brings a right of withdrawal, a real refund procedure and a good deal else with it. None of that is offered today, and if it ever is, this page and the terms change before it goes live, not after. --- # Frequently asked questions ## What is archeus? The memory and workspace layer for AI coding agents. archeus is free and open source, and it sits in front of the agent: you pick a project, see every session you have ever had in it, and launch the next one with the model, effort, permissions and context you intended. It adds persistent project memory, a session archive you can search and tag, MCP server management, an interactive architecture graph and per-turn cost tracking. It drives three coding CLIs — Anthropic’s Claude Code, OpenAI Codex and pi — with Claude Code much the deepest of them. It is a Python package, MIT licensed, with zero runtime dependencies, and it runs as a terminal UI or a desktop GUI over the same engine. ## Is archeus the same as claudectl? Yes. claudectl was renamed to archeus at version 2.0, in September 2026. Same project, same author, same code — the name changed because the tool had stopped being about one CLI. Everything migrates the first time you run it: settings, accounts, per-project launch defaults, the memory graph in each project, snapshots, plans and logs. Nothing is deleted, and the old command keeps working. If you already have the old package, pip install -U claudectl pulls archeus in and carries you across; its final release ships no code of its own, only the dependency. New installs should use pipx install archeus directly. ## Where does the name archeus come from, and what is it not? Archeus is Paracelsus’ name for the vital force that organises living matter — the thing that keeps a body coherent over time, which is what a memory layer does for a codebase. It is not Arceus, the Pokémon, which is spelled with the vowels the other way round and belongs to Nintendo. There is no connection to either. The software called archeus is this one: the Python memory and workspace layer for AI coding agents, installed with pipx install archeus, source at github.com/babarmuhammad/archeus. It is not affiliated with Anthropic. ## Is archeus made by Anthropic? No. archeus is an independent third-party tool built by Babar Muhammad Anas. It is not affiliated with, endorsed by, or supported by Anthropic. Claude and Claude Code are Anthropic’s trademarks. archeus wraps the Claude Code CLI you have already installed and uses the authentication you already have. ## Does archeus need an API key? Not by default. Out of the box it launches the CLI you already have — Claude Code, Codex or pi — using the login that CLI already holds, and it reads the transcripts and settings each one writes to disk — there is nothing to configure and no subscription. A key is only involved if you deliberately point a session somewhere else: archeus can route a session at a local model server, OpenRouter, OmniRoute or anything that serves POST /v1/messages, and that endpoint’s credentials are yours to supply. Everything runs on your machine either way, and the desktop GUI is served on loopback only. ## How do I install archeus on Windows, macOS or Linux? The same command on all three: pipx install archeus (or pip install archeus), or npx archeus to run it without installing it first. It needs Python 3.10 or newer and at least one coding CLI on your PATH — Claude Code, OpenAI Codex or pi. Then run archeus for the terminal UI, or archeus --gui for the desktop app. On Windows the GUI opens in a native window if PyQt6 is installed; everywhere else, and without PyQt6, it opens in your browser. ## Where does Claude Code store its sessions? Under your Claude Code config directory — ~/.claude by default, or wherever CLAUDE_CONFIG_DIR points — in projects//.jsonl, one JSON object per line. The folder name is your project path with every non-alphanumeric character replaced by a dash, which is lossy and should not be decoded; each transcript line already carries the real cwd. archeus reads these files directly and never modifies them. ## How do I see how many tokens Claude Code has used? archeus reads it out of your own transcripts rather than calling an API, so it works offline and covers every session you have ever run: usage per day, per project, per account and per model, with the cost of a turn broken down. The status line shows the current session’s context pressure and today’s spend on every turn. ## How do I uninstall archeus? pipx uninstall archeus, or pip uninstall archeus. If you installed the status line or hooks, remove them first from the hooks screen so the entries come out of Claude Code’s settings.json cleanly — archeus writes into that file and is the only thing that knows which entries are its own. Your sessions, memory and Claude Code configuration are untouched by the uninstall. ## Does archeus work with Claude Code plugins and skills? Yes, and it ships as one. archeus manages the marketplaces and plugins Claude Code has installed, and has its own plugin with slash commands and skills — /plugin marketplace add babarmuhammad/archeus. It deliberately bundles no hooks in the plugin: its hook manager already installs them per account, and two owners for one settings.json entry means the recall hook runs twice. ## How do I manage Claude Code sessions on Windows? Claude Code stores every session as a JSONL transcript under your config directory, but gives you no way to browse them. archeus lists every session per project with its topic, message count and age, and lets you search, tag, fork, resume, export and archive them — from a terminal UI or a desktop GUI. ## How do I reduce Claude Code token usage? The largest recurring cost is context you pay for on every message. archeus keeps the always-on CLAUDE.md block to a bounded index of about 250 tokens, moves per-module detail into path-scoped rules files that load only when Claude touches those files, and can inject only the memory subgraph a given prompt needs. Its own internal calls route to a cheap economy model, and Plan to Execute runs an expensive model once for the plan and a cheap one for execution. ## What is a Claude Code MCP server manager? MCP servers are configured in JSON and are otherwise invisible — you cannot easily tell which are connected to a project or what tools they expose. archeus detects the servers configured for each project, shows them at a glance, and can run an analysis that lists a server’s actual tools into your global CLAUDE.md inside a re-updatable block. ## How do I run multiple Claude accounts at the same time? Claude Code selects its account through the CLAUDE_CONFIG_DIR environment variable. archeus detects every configured account, merges their projects into one list, shows per-account usage side by side, and sets that variable for you when it launches a session, so you pick the account at launch rather than juggling environment variables. ## Does archeus require any Python dependencies? No. It runs on the Python standard library alone and needs Python 3.10 or newer. PyQt6 is optional and only needed for the native desktop shell — without it the GUI opens in your browser instead. ## Does archeus work on macOS and Linux, or only Windows? It runs on all three, and CI tests all three. It is Windows-first in that Windows gets the widest Python version matrix and the most real-world use, so rough edges are likelier on macOS and Linux. Bug reports from those platforms are welcome. ## How is archeus different from Claude Code’s built-in /resume? /resume reattaches you to a recent session in the current directory. archeus treats your sessions as a searchable archive across every project and every account, adds tags, export, fork and archive, and controls what model, effort, permissions and project context a session launches with. /resume answers "put me back"; archeus answers "what have I done here, and how should the next session start". ## Can I use a cheaper model to execute a plan made by a stronger model? Yes — that is what Plan to Execute does. It runs a headless planning pass with the strong model, shows you the plan for approval or editing, then hands the approved plan to a cheaper model to carry out. Free execution routes that half through OmniRoute’s free tier. ## What happens to my project memory after /compact? /compact discards detail from the conversation, which is where context loss usually bites. archeus’s memory lives outside the transcript — in the project’s memory graph and its rules files — so it is re-injected at the next launch regardless of what the conversation dropped. ## Does archeus have a GUI, or is it terminal-only? Both, over the same engine. The terminal UI is the default. Running archeus --gui opens a desktop GUI: a native window if PyQt6 is installed, otherwise your browser, served over loopback only. ## Is archeus free and open source? Yes, MIT licensed and free. It uses your existing Claude Code authentication and adds no subscription and no API key of its own. ## Can I install it from PyPI? Yes: pipx install archeus, or pip install archeus. It also ships as a Claude Code plugin if you would rather stay inside the session. ## Does archeus work with coding agents other than Claude Code? Yes — OpenAI Codex and pi, with nothing to configure: archeus looks for each CLI’s binary and its home directory, and one it cannot find is simply not offered. Their projects, sessions, previews, models and token spend merge into the same lists, one memory graph is delivered to all three (CLAUDE.md for Claude Code, AGENTS.md for Codex, both for pi), skills install into every one of them, and any of them can be launched from the same picker — each with its own model list and its own effort scale. Claude Code is much the deepest of the three: hooks, MCP servers, subagents, plugins, output styles and checkpoints are its alone, and every one of those screens says which CLI cannot do it and why rather than hiding the gap. The other half of the goal shipped earlier: a session can run against a local server, OpenRouter, OmniRoute or any endpoint that serves POST /v1/messages. Claude Code is the first surface, not the boundary, and it is no longer the only one — a harness of archeus’ own remains a long-term goal. ## Can I run archeus against a local model, or something other than Anthropic? Yes. Settings → Model provider takes any endpoint that serves POST /v1/messages — Ollama, llama.cpp, vLLM or another self-hosted server on your own machine, OpenRouter, or OmniRoute’s free tier — and a translating gateway handles backends that speak the OpenAI chat format instead. The session is still a real Claude Code session, so agents, skills, hooks, MCP servers, slash commands and checkpoints all keep working; what a backend swap genuinely costs is subagents, prompt caching, extended thinking and web_search, and that is written down rather than glossed. archeus’ own internal Claude calls — memory extraction, lesson distillation, review, the generators — can be routed there too, but that is opt-in and off by default, because they run unattended and moving them changes which account is billed. --- # Blog ## How to cut Claude Code token costs: the 250-token index pattern 2026-09-01 · claude-code, tokens, cost, context · https://claudectl.space/blog/cut-claude-code-token-costs Context files ride along on every message, so their size is a permanent per-turn tax. How to replace a 4,000-token CLAUDE.md with a bounded index. ### The short answer The expensive part of Claude Code is not the conversation, it is the context that gets re-sent with every message. `CLAUDE.md`, the global `~/.claude/CLAUDE.md`, always-loaded rules, a system-prompt file and every MCP server's tool schemas are all input on *each turn*, so their size is a fixed per-turn tax that grows monotonically as you add to them. The fix is structural, not editorial: keep a bounded always-on index of about 250 tokens, push per-module detail into path-scoped rules that load only when Claude touches a matching file, and inject task-specific knowledge per prompt inside a budget. Measure it first — most projects have never once looked at the number. ### Measure before you cut Nobody's estimate of their own `CLAUDE.md` is accurate. Run the context weight audit: ``` archeus # open the project, then ⇧W in the sessions menu ``` or the **Audit** tab in the GUI, which does the same thing project-scoped and account-scoped together. It itemises everything auto-loaded per turn: - `CLAUDE.md`, broken into its blocks — your prose, the AUTOGEN block, the session-topics log, the memory digest — each with its own token count - the global `~/.claude/CLAUDE.md`, which loads in *every* project on that account - `.claude/rules/*`, each marked **lazy** when glob-scoped - `system-prompt.txt` - `SessionStart` hook injections - MCP server tool schemas with a running always-on total and inline warnings: a `CLAUDE.md` over 200 lines, an unbounded session-topics block, a global `CLAUDE.md` that rides in every project. The two findings that show up most often are boring and large. First, a session-topics log that has been appending since January. Second, MCP servers whose tool schemas are in context on every turn whether or not you use them this session. ### The pattern: one bounded index, everything else lazy The failure mode of a hand-maintained context file is that it is asked to be two things at once — a stable set of instructions *and* an encyclopaedia of the codebase. Those have opposite cost profiles. Instructions are short and always relevant. Codebase knowledge is long and relevant maybe five percent of the time. Split them: | Tier | Content | Load | Budget | |---|---|---|---| | Always-on index | Repo one-liners, module names, a pointer to on-demand recall | Every message | **≤250 tokens** | | Path-scoped rules | Per-module entities, relations, conventions | Only when Claude opens a matching path | 0 until then | | Per-prompt injection | The subgraph this prompt actually needs | Once per prompt, opt-in hook | ≤600 tokens | The number that matters is not 250, it is *bounded*. A 250-token index that grows to 900 next quarter has failed. Keeping it flat takes two mechanisms: consolidation, which merges duplicate entities across modules, and an importance cap (`memory_max_entities`, default 500) that evicts the least-connected entity when the graph exceeds it. Per-repo rollup summaries — built locally, no model call — give the index its one-line-per-repo overview without listing everything underneath. The net effect is that the always-on cost stops tracking codebase size. A ten-module project and a sixty-module project pay the same per-turn tax; the sixty-module project just has more that is available lazily. ### Making the lazy tier actually lazy A rules file only costs nothing if Claude Code knows when it is irrelevant. That is what the `globs:` header does — the file is loaded when a matching path is touched and skipped otherwise. archeus generates one per module: ``` .claude/rules/archeus-mem--.md ``` each containing that module's entities, their types, one-line summaries and the relations between them. Several kilobytes across the set, zero per-turn cost. The audit marks these **lazy** for exactly this reason, and it is worth checking your own hand-written rules files for a missing `globs:` — a rule without one is always-on, and it will be sitting in the always-on column where you did not expect it. The GUI's Memory tab states the trade-off in one sentence it took a while to get right: *~N tok read on every turn, ~M tok across the rule files that cost nothing until Claude opens a matching path.* That is the whole design in a line. ### Prune the parts that grow forever Two blocks in a generated `CLAUDE.md` are unbounded by nature. **Session topics.** Every session adds a line. Nothing removes one. Capped now to the most recent N (`claude_md_sessions_cap`, default 10); `p` in the audit rebuilds the block in place. **The autogen commit list.** Configurable with `claude_md_commits`. Prune touches only those blocks. Your manual prose and the memory digest are untouched — which is the whole reason the file is structured as blocks rather than as text. In the GUI's CLAUDE.md tab each block shows its own token cost and its own regenerate button, so you can rebuild the session log without regenerating anything you wrote. ### Compress the prose you wrote by hand `⇧C` rewrites the hand-written portion into a lean lookup-table style, targeting under 500 tokens. It shows a before→after token count and a git-style diff to approve, keeps a `CLAUDE.md.bak`, and preserves the machine-maintained blocks verbatim. If part of your prose must survive verbatim — a legal note, a hard-won gotcha with exact wording — fence it: ```markdown Never regenerate the migration order. See docs/migrations.md. ``` A fenced section is not *sent to the model at all* during compression, so it cannot be reworded, shortened or dropped. That is a stronger guarantee than "the model was told to preserve it". ### Stop stray reads from blowing the window One `cat package-lock.json` can cost more than a week of `CLAUDE.md`. The audit's `d` action scans the project and writes `permissions.deny` rules into `.claude/settings.json` — `node_modules/**`, `dist/**`, lockfiles and the rest — merging into your existing settings rather than clobbering them. Two hooks attack the same class of waste from the other end: - **`concise-output`** — a `SessionStart` rule that suppresses narration and re-printed code. - **`filter-test-output`** — rewrites `pytest` / `npm test` / `go test` commands to pipe through a failures-only filter *before* the output reaches context. A green test run that prints 400 lines of dots is 400 lines of input tokens on the next turn. Both are one-key installs from the hooks manager. Note that a `SessionStart` hook that injects text is itself a per-turn cost, which is why the audit counts hook injections in the same table as everything else. ### Spend less per turn on the model itself Two levers, separate from context size. **A think cap and a cheap subagent model.** The launch-options screen exposes `MAX_THINKING_TOKENS` (Think cap) and `CLAUDE_CODE_SUBAGENT_MODEL` (Subagents). The `e` key applies an economy preset in one keystroke: Sonnet, an 8k thinking cap, Haiku subagents. Subagent model choice is underrated — a fan-out of six searches on the same model as your main session is six times the rate you meant to spend. **A cheap model for the tool's own calls.** archeus's internal Claude calls — memory extraction, lesson distillation, CLAUDE.md and hook and skill generation — default to Haiku (`extract_model` in Settings → Economy model). Your actual coding sessions keep whatever model you picked. Worth checking in any tool that makes model calls on your behalf: the default is often the same expensive model you use for the work. Beyond cheap there is free — plan on an accurate model and execute on a free one, covered in [plan on an expensive model, execute on a free one](/blog/plan-expensive-execute-cheap). ### Compact instructions Auto-compaction is going to happen on a long session. What it keeps is steerable: a `# Compact instructions` section in `CLAUDE.md` tells Claude Code what matters when it summarises. Scaffolded and AI-generated files include one; the audit offers to add it (`i`) when it is missing. It costs a handful of tokens per turn and changes what you still have after the compact, which is a good trade. ### An order of operations 1. Run `⇧W`. Write down the always-on total. 2. Cap or prune the session-topics block. Usually the largest single win, and it costs nothing. 3. Move per-module knowledge to `.claude/rules/` with `globs:`. Check the audit re-marks them lazy. 4. Compress the hand-written prose (`⇧C`), fencing anything that must survive word for word. 5. Generate deny rules (`d`) so one stray read cannot undo the rest. 6. Set the economy preset for routine work; keep the strong model for design. 7. Re-run `⇧W`. The number should be flat from here as the project grows, not rising. The point of the exercise is not a smaller file. It is that the always-on cost stops being a function of how big your codebase got. Reference detail: [docs.claudectl.space/usage](https://docs.claudectl.space/usage/). ## Managing Claude Code sessions on Windows 2026-09-01 · claude-code, windows, sessions, workflow · https://claudectl.space/blog/managing-claude-code-sessions-on-windows Claude Code writes every session to a JSONL file in an encoded folder and gives you no way to browse them. What is on disk, and how to work with it. ### The short answer Claude Code stores every session as a JSONL transcript under `%USERPROFILE%\.claude\projects\\.jsonl` and gives you no interface onto them beyond `/resume`, which lists recent sessions in the current directory only. There is no search, no naming, no tagging, no archive, and no view across projects. Managing them means either reading the JSONL yourself or putting a workspace layer in front — a browsable list per project, full-text search across every project, and per-project launch control. The Windows-specific pain is concentrated in three places: the lossy folder encoding, console encoding, and shortcut/elevation plumbing. ### What is on disk ``` %USERPROFILE%\.claude\ ├── projects\ │ └── D--Projects-my-app\ │ ├── 6aff4b52-....jsonl # the transcript │ ├── 6aff4b52-....name # display name (archeus) │ ├── tags.json # per-session tags (archeus) │ ├── system-prompt.txt # injected on every launch (archeus) │ ├── add-dirs.txt # --add-dir roots (archeus) │ └── archived\ # restorable archive (archeus) ├── settings.json # hooks, permissions, statusLine, outputStyle ├── CLAUDE.md # loaded in every session on this account └── file-history\\ # Claude Code's checkpoint store ``` The `.jsonl` files are the whole record: user turns, assistant turns, tool calls, tool results, one JSON object per line. They are append-only and nothing reads them back — Claude Code does not consult a prior transcript when you start a new session. They also get big. A 2,787-message session is a normal week. Any tool that touches these must stream rather than `readlines()`; archeus funnels every transcript read through one module (`transcripts.iter_json`) that yields objects with a `limit`/`offset`, a `max_bytes` cap, and a `prefilter` substring tested against the *raw line*, so a caller that only wants the `"Bash"` entries never pays for a `json.loads` it throws away. ### The folder name is lossy — do not decode it `D:\Projects\my-app` encodes to `D--Projects-my-app`. Every non-alphanumeric character maps to a dash, which means the encoding is one-way. Splitting on `--` to recover a drive letter works right up until it does not: ``` \\server\share\Project → --server-share-Project ``` The leading `--` makes the drive-letter split yield an empty drive, and every UNC-hosted project was silently dropped from the project list, dashboard, usage and search — no error anywhere, just an absent row. This is a real bug that shipped, and the fix is instructive for anyone writing their own tooling: **every transcript line already carries the real `cwd`**. Reading it is exact for every encoding, and cheaper than the recursive directory walk it replaces. Walk the filesystem only as a fallback for a project folder with no transcripts to read. If you are scripting against `~/.claude/projects` yourself, read `cwd` from line one of any `.jsonl` in the folder. Do not parse the folder name. ### Console encoding: the Windows tax Two independent bugs, same root cause, both worth knowing before you write a hook or a status line. **Writing.** Claude Code captures a hook's or status line's stdout as a *pipe*, so CPython picks the locale codepage rather than UTF-8 — cp1252 on Windows. A middle dot goes out as a bare `0xB7` and the terminal renders `Opus 5 � default �`. A block glyph like `▕` is worse: `UnicodeEncodeError`, exit 1, and a Python traceback parked under your prompt for the rest of the session. The fix is one line at the top of anything that prints: ```python sys.stdout.reconfigure(encoding='utf-8') ``` This only *looked* fine in development because `PYTHONIOENCODING=utf-8` was set in that environment. The regression test now strips that variable before running. **Reading.** The mirror image bites when you shell out to git. `subprocess.run(text=True)` with no `encoding=` decodes with the same codepage, so one non-ASCII branch name or path raises inside `subprocess` and the wrapper returns `None` — which surfaced to the user as "not a git repository" on a perfectly good repo. Pin it: ```python subprocess.run(cmd, text=True, encoding='utf-8', errors='ignore') ``` archeus routes every git call through a single function for exactly this reason; one door, one place to get the encoding right. ### Repo discovery: `.git` is not always a directory If your workflow involves submodules or worktrees — common on Windows monorepos — this one matters. Testing `isdir('.git')` misses both. A submodule *and* a linked worktree store `.git` as a **file** containing one `gitdir:` line, and that line is the whole classifier: | `gitdir:` contains | It is | |---|---| | `.git/modules/` | a submodule | | `.git/worktrees/` | a linked worktree | No subprocess needed to tell them apart. Getting this wrong in the obvious direction is also a trap: counting linked worktrees as repos adds every copy of a repo to its own repo list. And `git worktree list` run *inside a submodule* reports the gitdir (`…/.git/modules/`), not the working directory, which quietly breaks any join from session to path. ### What a workspace layer adds With that on-disk picture, the useful operations are the ones Claude Code does not expose. In archeus's session menu: | Key | Action | |---|---| | `/` | Action palette — every action, type-to-filter | | `r` | Rename session (writes `.name`) | | `t` | Tag session (tags show inline and are searchable) | | `f` | Fork session | | `d` | Archive (moves to `archived/`, restorable) | | `v` | View transcript in a pager, `/` searches inside it | | `e` | Export the transcript to markdown | | `i` | Session info — tokens, estimated cost, models, git branch, duration | | `F` | Changed files, derived from the session's tool calls | | `u` | Project usage stats | | `⇧K` | Hand off: new session seeded with another session's context | Two of those are worth calling out. **Changed files** (`F`) is derived from tool calls in the transcript, so it answers "what did that session actually touch" without a git diff — useful when the session ran across a branch switch. And the transcript viewer's position counter reads `msg N/M`, counting conversation messages rather than raw lines, because most lines in a `.jsonl` are tool traffic. Cross-project search is separate from per-project filtering: **🔍 Search all sessions** indexes names, AI-generated titles and previews across every project, caches the index so later opens are instant, and resumes the match directly regardless of which project it lives in. ### Usage, read from your own transcripts Everything needed to cost a session is already in the file — tokens in, out, cache, per model. archeus's usage dashboard parses local transcripts (no API call) into a per-project and per-session table with estimated cost at published API rates, plus a per-day table of the last 14 days. On a subscription plan those numbers are not a bill, they are a consumption gauge — which is the right way to read them. The plan-usage bars on the main screen are a different source: they come from the rate-limit windows Claude Code itself reports. ### Windows shell plumbing **Pin to taskbar (Windows 11).** Windows 11 will not pin a `.bat` shortcut, so the shortcut must target `cmd.exe`: ```powershell $shell = New-Object -ComObject WScript.Shell $lnk = $shell.CreateShortcut("$env:USERPROFILE\Desktop\Open Repo Claude.lnk") $lnk.TargetPath = "C:\Windows\System32\cmd.exe" $lnk.Arguments = "/c `"$PWD\Open Repo cmd.bat`"" $lnk.WorkingDirectory = "$PWD" $lnk.IconLocation = "$PWD\claude_sessions\archeus.ico, 0" $lnk.Save() ``` Then right-click the desktop shortcut → **Pin to taskbar**. **Elevation without a UAC prompt every time.** If `claude.exe` or your project paths need admin rights, ticking "Run as administrator" on a shortcut gives you a UAC prompt on every launch. Register a scheduled task that already runs at highest privilege and point the shortcut at `schtasks /run /tn "Archeus"` instead; leave the shortcut's own elevation checkbox *unticked*, because `schtasks.exe` does not need elevating, only the task it triggers. Launch the task through `wt.exe` rather than `cmd.exe` directly — elevated console apps otherwise fall back to legacy conhost, which renders a TUI with broken colours and box-drawing. **No console window.** `pythonw.exe` runs the GUI without one; the full shortcut recipe including the GUI icon is in [the install guide](https://docs.claudectl.space/installation/). **Killing a session tree.** If you script session launches, note that `taskkill /T` can report failure, and a fallback to `Popen.kill` is not optional. The POSIX equivalent has a sharper edge: `os.killpg` may only be used when the child actually leads its own process group — otherwise its group is *yours*, and the tree kill takes your own tool down with it. ### Terminal or window The same operations exist in both interfaces over one engine. `archeus` opens the TUI; `archeus --gui` opens a desktop window — PyQt6 native if installed, otherwise an Edge app-mode window, otherwise your browser, served on loopback only. Neither needs a third-party Python package; archeus is standard library only, and uses the Claude Code authentication you already have. Full key map and command line: [docs.claudectl.space/usage](https://docs.claudectl.space/usage/). ## Running multiple Claude accounts without losing your place 2026-09-01 · claude-code, accounts, workflow · https://claudectl.space/blog/multiple-claude-accounts Claude Code picks its account from CLAUDE_CONFIG_DIR. Here is how to run two or more side by side without losing sessions, memory or usage history. ### The short answer Claude Code decides which account it is using from one environment variable: `CLAUDE_CONFIG_DIR`, the directory holding that account's credentials, `settings.json`, `CLAUDE.md` and `projects/` folder. Point it somewhere else and run `claude` to log in, and you have a second account; set it per-launch instead of globally and both accounts run at the same time. The variable is the easy part. What breaks is everything built around it — your session history, project memory, hooks and usage totals all split along the same line, and most tooling silently reports only whichever account was active when it started up. ### The mechanism ```powershell # account one — the default %USERPROFILE%\.claude\ # account two $env:CLAUDE_CONFIG_DIR = "$env:USERPROFILE\.claude-work" claude # /login here creates the second account's credentials ``` Everything account-shaped lives under that directory: credentials, `settings.json` (which is where hooks, permissions, `statusLine` and `outputStyle` live), the account-wide `CLAUDE.md`, and `projects//` with every session transcript for that account. That has a consequence worth stating plainly, because it surprises people: **the same project opened under two accounts has two separate session folders**. Nothing is lost, but nothing is joined either. Your history for `D:\Projects\my-app` is in two places, and `/resume` will only ever show you one of them. ### What archeus does with that The tooling problem is not switching — it is not noticing that you switched. **Named accounts.** ⚙ Accounts: add an account with a name and a config dir (archeus creates the directory and can open `/login` immediately), rename it, switch the active one, or **open it in a new terminal with one key** so both accounts are running at the same time. **Per-launch account.** The launch-options screen has an **Account** field. Pick which account *this* session starts under without changing your default. The field only appears when more than one account exists. **One row per project, not per account.** If the same folder has sessions under two accounts, the project list shows a single row — default account primary, tagged `[+work]` — instead of a duplicate. Opening it merges every account's sessions into one list, with foreign-account sessions marked inline (`[work]`). Rename, archive, delete, fork and view all act on that session's own account, and resuming one launches under the right account automatically. That last detail is what makes the merge safe rather than confusing: the list is unified, the actions are not. **One usage bar per account.** The plan-usage banner shows a bar row per account, labelled by email or name, with each account's session and weekly windows and their reset times. A single account stays a single compact bar. **Account-accurate memory.** The memory graph lives under the project's *real path*, shared by every account, and the features that feed it read **every** account's sessions: lesson extraction, the CLAUDE.md session-topics block, per-project usage stats, workspace freshness counts and the recent-sessions quick-resume list. A project used under two accounts is one merged row in the usage dashboard, not two. **Levelling up.** `archeus sync-accounts` places what you have actually provisioned — hooks, status line, settings — into every account's config dir. `--dry-run` first if you want to see it before it happens. ### The rate-limit workaround, properly This is the case that makes multi-account worth the setup. An account hits its 5-hour or weekly window mid-task. The work is not lost — the transcript is already on disk — but the session is over. **Hand off** on the session row in the desktop app, or `⇧K` in the sessions menu of the terminal UI: 1. **Pick the source session.** In the desktop app the row *is* the source — the sessions list already spans *every* configured account, each row labelled with its own: `[work] Refactor the parser (2h ago)`. Foreign-account sessions are in that list on purpose; that is the entire point. In the terminal UI, `⇧K` opens a picker over the same set, newest first. 2. **Pick the target account.** Which account the *new* session launches under. Defaults to the project's current account, and only asks at all when more than one is configured. 3. **archeus writes the transcript to disk** — `/.archeus/injected-context.md`, headed with the source session's title and originating account, then the whole conversation as `### User` / `### Assistant` sections. 4. **The new session launches with a pointer, not a paste.** It gets a short system-prompt line saying where the file is and to read it first. The transcript never goes on the command line — a long one would blow past the Windows argv limit — so the model reads the file with its own tools. The new session inherits the project's usual launch setup read from the **target** account's project folder: default model, permission mode, `--add-dir` roots, extra PATH entries, and that project's `system-prompt.txt`, merged with the pointer rather than replacing it. The same flow covers two other situations: "the context window filled and `/compact` ate the details", and "that exploration went well, start a real session from it". Notes worth knowing: the context file is rewritten each hand-off, so only the most recent injection is on disk; `.archeus/` is machine-local and belongs in `.gitignore`; and usage from the new session counts against the target account, which is the whole idea. ### The bug class this feature attracts Multi-account support fails in one characteristic way, and it is worth describing because it will bite anyone writing their own scripts around `CLAUDE_CONFIG_DIR`. All three of the following were found chasing a single report — "the status line only works on the default account" — and all three are the same mistake in different costumes. **A module-level path constant.** A line like ```python settings_path = os.path.join(config_dir, 'settings.json') # evaluated at import ``` means every reader and writer of that file can only ever touch the account that was active when the module was imported. With three accounts configured, two of them had no status line and **no hooks at all**, while every surface confidently reported "installed". The fix is a function — `settings_path_for(cfgdir)` — plus load and save that take the directory as an argument. **Wrong precedence.** The config-directory resolver checked a saved setting and a default, and never looked at `CLAUDE_CONFIG_DIR` at all. But archeus itself *writes* that variable at five different spawn sites to choose the account — so a status line running inside a session launched under account B resolved archeus's saved setting and labelled every session with account A. Precedence must be **environment > setting > default**, which is also Claude Code's own order. **Frozen colours.** `from .config import C_WARN as _WARN` freezes a value at import; the theme system rebinds `config.C_*` later and the frozen copy never moves. Harmless-looking, and it made a test pass alone and fail in a full run depending on collection order. The general rule underneath all three: **a module-level constant derived from mutable state is a cache with no invalidation. Derive it in a function.** There is a corollary for anyone running a test suite from inside a Claude Code session: `CLAUDE_CONFIG_DIR` is set in that environment, so tests that resolve the config directory pick up your live account. archeus's `conftest.py` pops the variable at *module* level rather than in a fixture, because a fixture runs too late for anything captured at import. ### One security note If you build a local API around this — archeus's GUI is one — the config directory becomes a parameter, and it is joined with `projects` and a project name across dozens of endpoints. That makes it a filesystem read primitive. It has to be validated against the set of accounts the tool actually knows about, not accepted as an arbitrary path. A missing or unknown value should be a `400`, not a `500` and not a directory walk. ### When it is not worth it Two accounts is real overhead: two logins, two `settings.json` files to keep level, two sets of hooks. If you never hit a rate limit and never need to separate client work from personal work, one account and a good session archive is the simpler system. The setup earns its keep at the moment a five-hour window closes on you mid-task and the answer is one keypress instead of an afternoon. Reference detail: [docs.claudectl.space/accounts](https://docs.claudectl.space/accounts/) and [the context hand-off page](https://docs.claudectl.space/context-handoff/). ## Plan on an expensive model, execute on a free one 2026-09-01 · claude-code, cost, models, workflow · https://claudectl.space/blog/plan-expensive-execute-cheap Run the expensive reasoning once, then hand the approved plan to a cheap or free model to build. How the two-model split works and where it breaks. ### The short answer Most of the token cost of an agentic coding session is not the thinking, it is the doing — reading files, applying edits, running tests, fixing the typo, running them again. Those are different jobs with different model requirements. So split them: run one headless planning pass on an accurate model, review and edit the plan, save it to a file, then launch a *real* interactive session on a cheap or free model seeded to read and execute that file. Expensive reasoning happens once. The build runs on the cheap tier. The interesting engineering is entirely in the hand-off and in what breaks when the executing model is weak. ### The flow `⇧X` in the terminal UI, or the **Plan → Execute** project tab in the GUI. 1. **Describe the task.** One prompt, as you would to any session. 2. **archeus plans it headlessly** with `plan_model` — default Opus 5, reasoning effort picked per task. No interactive session, no back-and-forth; one pass producing a plan. 3. **You approve, edit or re-plan.** The plan appears in a monospace textarea for inline editing. "Re-plan" sends your feedback back for a regeneration. Every generated plan is auto-saved. 4. **The plan is written to `.archeus/plan-latest.md`.** 5. **A real, full interactive `claude` session launches** on `exec_model` — default Sonnet 5 — with the same account, agents, skills, system prompt and `--add-dir` roots the project already has, seeded to read and execute that plan. Point 5 is the part worth being precise about. The execute half is not a headless `-p` run and not a constrained sub-mode. It is the ordinary interactive session you would have started anyway, with one extra instruction. You can interrupt it, argue with it, and take over. Optionally, **per-step approval** gates execution step by step rather than letting it run the whole plan. ### Why the plan is a pointer, not a paste The obvious implementation is to put the plan in the launch prompt. It fails on Windows: a long plan on the command line runs past the argv length limit and the launch dies, or worse, truncates. So the plan goes to a path and the session gets a short system-prompt line saying where it is and to read it first. Three benefits fall out of that: - The command line stays short regardless of plan size. - The model pulls the file in with its own tools, so it can **re-read** the plan mid-session when it loses the thread. - The same mechanism serves the context hand-off feature, which writes a whole prior transcript to `.archeus/injected-context.md` and hands over a pointer to it for exactly the same reason. `.archeus/` is machine-local. Add it to `.gitignore` if the project does not already ignore it. ### Free execution via OmniRoute Cheap is Sonnet. Free is [OmniRoute](https://github.com/diegosouzapw/OmniRoute) — a local proxy aggregating free-tier providers behind one endpoint. Point the execute half at it and the build phase costs nothing against your Anthropic quota. Left on **Auto** (the default), OmniRoute scores every currently-healthy free model per request on health, quota, cost, latency and task fit, and transparently falls back to the next-best one when the current one is rate-limited or exhausted. archeus auto-starts it in the background the first time you route a task through it, so there is no terminal to babysit. Setup, once: ```bash npm install -g omniroute omniroute setup --password omniroute # or let archeus start it # → http://localhost:20128 → log in → Providers → Add Provider / Free tiers ``` Then in archeus's **Settings → Free execution — OmniRoute**: leave the base URL at `http://localhost:20128`, click **Refresh**, leave **Execute model** on *Auto*, save. Open a project's **Plan → Execute** tab, describe a task, pick **Execute via → OmniRoute**, approve the plan. Three honest caveats about that setup, all confirmed rather than theoretical: - OmniRoute's marketing claims roughly 90 free providers. What is actually reachable **without a real signup** is a smaller genuinely-keyless subset — Pollinations, Puter, NVIDIA, OpenCode, FriendliAI, Coze and a few more. Check the current list in the dashboard yourself. - The `omniroute providers add` CLI commands crash on Windows. The dashboard is the only reliable path, which is also why archeus never touches that credential. - OmniRoute's own per-connection self-check reports false negatives — it will call a working no-auth connection broken. Use **Send a live test** for the authoritative answer. Beyond the plan-execute split, archeus can launch a **standalone** interactive session through OmniRoute: open a project in the TUI and pick a model from the **OMNIROUTE** menu, which appears only when OmniRoute is reachable. ### What actually breaks on a free model This is the section that decides whether the idea is useful to you. **Small context windows.** Free-tier models frequently sit under 16K tokens. Your `CLAUDE.md` plus rules files plus the plan has to fit inside that with room for the work. archeus warns when the total passes roughly 8K. This is where a bounded always-on index stops being a nice-to-have and becomes the thing that makes free execution possible at all — see [the 250-token index pattern](/blog/cut-claude-code-token-costs). **Missing tool use.** Some free models have no `tool_use`, which degrades agents, skills and MCP calls. archeus automatically sets `CLAUDE_CODE_SUBAGENT_MODEL=claude-sonnet-5`, so delegated work always runs on a capable model even when the main session does not. That covers the common case; the main model's own capabilities remain whatever the free model has. **Telemetry the provider rejects.** `CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1` is set automatically, because non-essential calls to the Anthropic API will fail in this configuration. **Cost tracking splits.** Anthropic usage tracking does not see OmniRoute spend. Your usage dashboard is telling the truth about the account and nothing about the free tier. What *does* work unchanged: per-project memory, hooks, MCP servers and skills. All of them are client-side and model-agnostic — they load from `CLAUDE_CONFIG_DIR` and the project's `.claude/` exactly as usual. ### When the model dies mid-session A free model failing at launch is a bad model choice. A free model failing on turn forty is a different problem, and Claude Code cannot solve it: it sends each turn as a fresh request and, when one fails, retries **the same request against the same model** with backoff. Nothing ever tries a different model, because Claude Code has no such concept. A model deregistered upstream, or a tool schema the backing provider rejects, makes the session look frozen forever. archeus's failover proxy sits between `claude.exe` and the OmniRoute upstream. It forwards bytes verbatim, and when a turn errors **before any response body byte has reached the client**, it rewrites the request's `model` field and tries the next candidate. That "before any byte" condition is the whole correctness argument — once the client has seen part of a response you cannot retry without duplicating output. The elegant part is that no per-turn machinery was needed: because every turn *is* its own request, request-level retry already is per-turn failover. ``` archeus --failover-serve [port] # foreground archeus --failover-stop # terminate the daemon in the lock file ``` It runs as a detached child, so closing archeus does not leave every live session with connection-refused, and it binds `127.0.0.1` only. The routing log runs in its own console window unless you hide it — deliberately, because the original complaint was never "a model died", it was "I could not see that a model died". **One security note, because this is the kind of thing that gets built carelessly.** An early version of this proxy was a second HTTP server on a fixed, source-published port with no guard at all, substituting the user's OmniRoute key into everything it forwarded. A single CORS-simple `fetch()` from any open browser tab would have spent that quota. "It's loopback" is not an authorisation boundary and neither is a custom header — under DNS rebinding an attacker's page becomes same-origin with your local server and can send any header it likes. The layering that works, cheapest check first: a `Host` allowlist (this is the rebinding defence, because a rebound request carries the attacker's hostname), rejection of anything carrying browser fetch metadata (`Origin`, `Referer`, `Sec-Fetch-*` — Claude Code's HTTP client sends none of them, every browser sends at least one), and a per-run secret compared with `hmac.compare_digest`. If you build a local proxy that spends money, apply all three. ### Telling afterwards which sessions ran free Worth knowing if you analyse your own transcripts. OmniRoute records the *resolved provider* model in `message.model` under a bare name — `big-pickle`, `deepseek-v4-flash-free`, `mimo-auto` — **not** the slash-namespaced id you selected at launch. So looking for a `/` finds nothing. The reliable signal is exclusion: a model is a free-tier run if its id is not a Claude or Anthropic one (no `claude`/`anthropic` substring, and not a bare alias like `sonnet`/`opus`/`haiku`). archeus tags those sessions in the session list. The unavoidable caveat: Anthropic served *through* OmniRoute is indistinguishable from a direct Anthropic run by id alone. ### When to use it Use the split when the task is well-specified and long — a refactor across twenty files, a migration, a mechanical API change. The plan is where the judgement is, the execution is typing. Do not use it when you do not yet know what you want. A plan produced from a vague prompt is a confident wrong plan, and handing it to a cheap model gets you a lot of wrong work quickly. For exploratory work, one good model and a conversation is still the right shape. Reference: [docs.claudectl.space/plan-execute](https://docs.claudectl.space/plan-execute/) and [the failover section](https://docs.claudectl.space/statusline/). ## Reading Claude Code's own state: sessions, plugins, checkpoints, settings 2026-09-01 · claude-code, internals, tooling · https://claudectl.space/blog/reading-claude-codes-own-state Claude Code keeps transcripts, checkpoints, plugin caches and settings on disk in formats it never documented. How to read them without breaking them. ### The short answer Claude Code keeps a lot on disk — session transcripts, `settings.json`, checkpoint snapshots, plugin caches, per-project metadata — and almost none of the formats are documented. That makes them fair game to *read* and dangerous to build on. The discipline that works is narrow: read the state, but never depend on the naming scheme; re-derive keys from data you already have instead of decoding them; treat any file another program also writes as read-modify-write with an atomic replace; and when a store stops matching your expectations, report that you cannot read it rather than guessing. Every rule below came from getting one of them wrong first. ### Transcripts ``` /projects//.jsonl ``` One JSON object per line: user turns, assistant turns, tool calls, tool results. These are the richest source of truth on the machine — they carry the model used, token counts, the working directory, and every file the session touched. Two rules. **Stream them.** They get large; a 2,787-message session is a normal week's work and files past 100 MB exist. `readlines()` on one of those is a memory event, not a read. One reader for the format, and the reader streams — with `limit`/`offset` over yielded objects, a `max_bytes` cap, and a substring prefilter tested against the *raw line* so a caller that only wants the `"Bash"` entries never pays for a `json.loads` it discards. **Do not decode the folder name.** The encoding is lossy: `D:\Projects\my-app` becomes `D--Projects-my-app`, and `\\server\share\Project` becomes `--server-share-Project`, whose leading dashes defeat any attempt to recover a drive letter. Every transcript line already carries the real `cwd`. Read it. That is exact for every encoding and cheaper than the directory walk it replaces. (More on the Windows specifics in [managing Claude Code sessions on Windows](/blog/managing-claude-code-sessions-on-windows).) ### `.claude.json` Per-project metadata. Two details will burn you. **Keys are real paths with forward slashes, even on Windows.** Not the encoded folder name, not backslashes. **Every timestamp is epoch milliseconds.** Read as seconds, the age comes out hugely negative — and a hugely negative age renders as "just now", which looks completely plausible. That is what makes it survive code review: the bug's output is indistinguishable from correct output for recent projects. The only test that catches it uses a stamp that is *definitely old* and asserts the age is large, rather than generating a timestamp inside the test. ### Checkpoints Claude Code takes whole-file snapshots before it edits, in: ``` ~/.claude/file-history//@v ``` The store is undocumented. `` is `sha256()[:16]` — established empirically by hashing a real transcript's paths against a real directory and checking the match: 6 of 6 matched, 0 left over. Knowing that, the tempting move is to decode: walk the snapshot directory and reverse the names into filenames. You cannot — SHA-256 does not run backwards — and any heuristic pairing is a guess presented as a fact. The correct move is the inverse. **Hash the paths this session edited** (they are in the transcript) and look those hashes up in the store. The property that buys you is graceful failure: if Anthropic changes the scheme, nothing matches, and the tool reports `recognised: False` — "cannot read the store" — instead of confidently pairing snapshots with filenames at random. Read-only, too. Restoring is `/rewind`'s job; archeus enforces this with a test that greps the module for any write call, because "we only read it" is a claim that decays the moment someone adds a convenience feature. ### Plugin caches ``` /plugins/known_marketplaces.json /plugins/installed_plugins.json ``` Readable, and worth reading — showing which plugins are installed and which marketplaces are known is genuinely useful. But **the format has already changed once**, which is the entire argument for the policy: every *mutation* shells out to the `claude` CLI rather than editing the JSON. That is the general shape of the rule. Reading an undocumented file costs you a stale display when it changes. Writing one costs you a corrupted install. ### `settings.json` The most consequential file, because it is not yours. Hooks, permissions, `outputStyle` and `statusLine` all live in it together, and it is Claude Code's own file. **Read-modify-write, always.** A script that writes the whole file erases whatever it did not know about. If you are adding a hook, load the current contents, insert, write back. **Atomically.** A plain `open(path, 'w')` that dies partway leaves truncated JSON and breaks the user's entire Claude Code session, not just your tool. Temp file plus `os.replace`: ```python def write_json_atomic(path, data): tmp = path + '.tmp' with open(tmp, 'w', encoding='utf-8') as f: json.dump(data, f, indent=2) os.replace(tmp, path) # atomic on the same filesystem ``` archeus had three modules that had each solved this locally before it was consolidated into one helper with eleven call sites and an AST-walking test that fails when any writer near anything named "settings" uses a plain `open(..., 'w')` — because five of them were written the plain way one at a time, and a sixth would have been. **A missing file is an empty state; a file that will not parse is a fault.** Seven modules in this codebase had written `except Exception: return {}` around a `json.load`, and every one of those readers was also a writer — so the first truncated write was also the last time the data existed. The one that mattered most read Claude Code's `settings.json`: returning `{}` for an unparseable file erased the user's hooks, permissions and output style on the next save. The fix is to move a corrupt file aside as `.corrupt-` and report it. **Removing a key is sometimes the correct write.** Setting `outputStyle` to `'default'` is done by *deleting* the key, not writing the literal — pinning the literal freezes behaviour against a future change in what "default" means. ### Status line payloads If you write a status line, Claude Code hands you a JSON payload on stdin on **every turn**. It already contains more than most implementations use: | Field | What it is | |---|---| | `context_window.used_percentage` | How full the context window is | | `rate_limits.five_hour` | The 5-hour window | | `rate_limits.seven_day` | The 7-day window | There is a lesson attached to that table. archeus's status line originally read `context_used_pct`, then `contextUsedPercent`, then `context.used` / `context.total`. Claude Code has never sent any of them. The segment was dead in production for its entire life — and its test passed, because the test asserted the same invented shape. It was testing the bug rather than the behaviour. **Check field names against the payload you actually receive.** A plausible wrong name renders nothing, and nothing looks like "no data yet". The regression test now asserts the dead names render *nothing*, which is the only assertion that would have caught it. The rate-limit fields are the second half of the same discovery: they were already in the payload while a background OAuth poll was being paid for to fetch the same numbers. Because this runs per turn, cost matters. archeus dispatches the `statusline` subcommand *before* importing its main module, which drags in `urllib`/`ssl`/`http.client` for a poll the status line must never make — that alone took the round trip from about 125ms to 58ms. The git branch is read straight from `.git/HEAD`, which is cheaper than a cache lookup and can never be stale; everything else comes from a disk cache that is guaranteed never to spawn a subprocess. ### When the docs and the disk disagree The sharpest example. Anthropic's documentation for agent teams says a team is named `session-` plus the first 8 characters of the session id. On the machine this was tested on, `teams/` is **empty** and `tasks/` is keyed by the **full session UUID**. Docs and disk already disagree, so the only defensible behaviour is to report a task directory as itself and never join it to a team by a rule the data does not support. Same discipline as the checkpoint store: when the shape you expected is not there, say so. This is the principle that ties all of the above together, and it is worth stating as a rule you can apply to any undocumented store: > Read it. Do not build on its naming. Re-derive keys from data you already hold. Write only through the tool that owns the format, and only atomically. When it stops matching, report unreadable — never guess. ### One more, for your own files If your tool has its own settings file, `load_settings` must carry the keys it does not recognise. Save writes back whatever load returned, so filtering unknown keys on read means an older version of your tool **silently erases** a newer one's settings — and syncing a config file between two machines is enough to trigger it. Reference: [docs.claudectl.space/reference](https://docs.claudectl.space/reference/) for the per-project file layout and session encoding, [docs.claudectl.space/statusline](https://docs.claudectl.space/statusline/) for the status line and checkpoint viewer. ## What belongs in CLAUDE.md, and what belongs in path-scoped rules 2026-09-01 · claude-code, claude-md, context, conventions · https://claudectl.space/blog/what-belongs-in-claude-md CLAUDE.md is read on every turn; path-scoped rules cost nothing until Claude opens a matching file. A working split, block by block, with token costs. ### The short answer `CLAUDE.md` is read on every turn, so it should hold only what is true for the whole project and needed almost always: build and test commands, the conventions Claude keeps violating, hard constraints, and a pointer to where deeper knowledge lives. Everything scoped to one module, one language or one directory belongs in a `.claude/rules/*.md` file with a `globs:` header, which Claude Code loads only when it touches a matching path — several kilobytes of detail at zero cost until it is relevant. The split is not stylistic. It is the difference between a context cost that grows with your codebase and one that does not. ### The loading model Before deciding what goes where, know what actually gets read. Claude Code stacks several instruction files: | File | Scope | |---|---| | `~/.claude/CLAUDE.md` | Every session on that account, every project | | `/CLAUDE.md` | The project | | `/.claude/CLAUDE.md` | The project | | Local variant | The project, not committed | | `.claude/rules/*.md` | Path-scoped when they carry `globs:`, otherwise always-on | Each can pull in more with `@import`. The failure mode there is quiet: an `@import` pointing at a file that no longer exists loads nothing and reports nothing. archeus's memory map (`M` in the sessions menu, or the CLAUDE.md tab in the GUI) lists which files load for a project and flags broken imports as **missing**, which is the only way to find them short of reading each one. The account-level file deserves particular suspicion, because it costs you on every project. archeus uses it for MCP tool documentation, written in per-server sentinel blocks so re-running the analysis updates one server's section and leaves the rest alone: ``` ### MCP: Notion … tool listing … ``` If you have a convention that only matters in one repo, it does not belong there. ### The decision rule For each thing you want Claude to know, ask two questions: **is it true everywhere in this project**, and **is it needed on most turns**? Two yeses means `CLAUDE.md`. Otherwise, further out. | Content | Where | Why | |---|---|---| | Build, test, lint commands | `CLAUDE.md` | Needed constantly, project-wide, tiny | | "This machine uses PowerShell 5.1, no `&&`" | `~/.claude/CLAUDE.md` | True on every project on this machine | | Hard constraints ("never rewrite `settings.json` wholesale") | `CLAUDE.md` | Cheap, and the cost of violating it is high | | Per-module architecture, entities, relations | `.claude/rules/` with `globs:` | Only relevant when that module is open | | "React components use the `use-` prefix" | `.claude/rules/` scoped to `src/components/**` | Language- and directory-specific | | A workflow with steps ("how to cut a release") | `.claude/skills//SKILL.md` | Loaded on demand, by name | | The file tree | Nowhere hand-written | Goes stale; generate it or let the model look | | API endpoint catalogue | Generated, or nowhere | Same | The last two rows are a lesson learned the expensive way. A hand-maintained document in this repo catalogued seventeen HTTP routes that had never existed and design problems that had since been fixed. It was deleted; the equivalent is now generated from the route tables themselves, with a test that fails when it is stale. **A hand-maintained copy of something the code already states will be wrong**, and a wrong file tree is worse than no file tree because the model trusts it. ### What a good CLAUDE.md entry looks like The entries that earn their tokens are not style preferences. They are the gotchas a competent agent would otherwise rediscover, written with the *reason* attached, because a rule without a reason gets rationalised away in edge cases. A real one from this project: > **Only `paint()`/`paintNow()` may write `#content` — a convention in 15 call sites is not a guarantee.** Almost every renderer paints a spinner, awaits a fetch, then writes the real markup; nothing stopped that second write landing after you had navigated away, so a slow page overwrote the one you were on. The guard is now inside the two functions themselves. `test_no_page_can_paint_over_the_one_you_are_on` counts the raw writes and fails at three. Three things make that worth its tokens. It names the symptom, so the model recognises the situation. It names the root cause, so the model does not fix it in the wrong place. And it names the **test that enforces it**, which converts a request into a verifiable constraint — the model can run it. The counter-example is a paragraph of adjectives: "we care about clean, maintainable code". Zero information per token. The Bash heredoc entry is another good shape — it exists purely because the failure is invisible: > **The Bash tool's heredoc eats backslashes; use `Write` for patch scripts.** `py - <<'PY'` collapsed `\\n` to a literal newline twice in one session, once producing an unterminated string literal and once silently failing an `assert` so a patch was lost. You would not guess that from first principles, and the model will hit it again next week. ### Path-scoped rules in practice A rules file is markdown with a header that scopes it: ```markdown --- globs: "claude_sessions/memory*.py" --- # Memory subsystem - The graph lives at `.archeus/memory/graph.json`. - Consolidation merges duplicate entities across modules before the importance cap evicts anything. - Never write the graph without going through the atomic helper. ``` Loaded when Claude opens a matching file; absent otherwise. This is where per-module knowledge belongs, and archeus generates one per module from its memory graph — entities, their types, one-line summaries, and the relations between them: ``` .claude/rules/archeus-mem--.md ``` The one thing to check on your own hand-written rules: **a rules file with no `globs:` is always-on**, and will be sitting in the same cost column as `CLAUDE.md` where you did not expect it. archeus's context weight audit marks each rule *lazy* or not, which is the fastest way to catch one. ### CLAUDE.md as blocks, not as text Once a file mixes your prose with generated content, treating it as one blob makes every regeneration a risk. Structure it as blocks with sentinels: | Block | Written by | Regenerated by | |---|---|---| | Your prose | You | Nothing | | `` | You, fenced | Nothing, ever | | `` | Scaffold — git repos, recent commits, README heads | Scaffold | | `` | Session topics | Prune | | `` | The memory digest | Memory build | The GUI's CLAUDE.md tab shows the file as exactly this — each block with its own token cost and the one button that regenerates that block alone. Pruning the session log does not touch your prose; rebuilding the memory digest does not touch the autogen block. The KEEP fence is the strongest of these. AI compression does not merely *avoid rewriting* a fenced section, it never sends it to the model at all, so it cannot be reworded, shortened or dropped. Use it for anything whose exact wording matters. ### The two blocks that grow forever If your `CLAUDE.md` is generated in part, two blocks are unbounded by nature: the session-topics log (one line per session, nothing ever removed) and the commit list. Cap both — `claude_md_sessions_cap` defaults to 10 recent entries, `claude_md_commits` sets the commit count. This is the single most common cause of a file that was 60 lines in March and 300 in August without anyone writing a word. The cost side of all this — measuring the always-on total, compression, deny rules — is covered in [the 250-token index pattern](/blog/cut-claude-code-token-costs). ### Compact instructions One section that is worth its per-turn cost on any long-running project: `# Compact instructions`, which steers what Claude Code's auto-compaction keeps when the context window fills. It costs a handful of tokens per turn and changes what you still have on the other side of a compact. archeus's scaffolded and AI-generated files include one, and the audit offers to add it when missing. ### A shape that works ```markdown # Project ### Commands build: `npm run build` · test: `npm test` · lint: `npm run lint` ### Constraints - Never edit `db/schema.sql` by hand; migrations only (`npm run migrate:new`). - All money values are integer cents. `test_no_float_money` enforces it. ### Gotchas - **The worker pool must be drained before exit.** …symptom, cause, guard… ### Compact instructions Keep: the current task, file paths touched, decisions made. Drop: tool output. …bounded generated index: repos, modules, a recall pointer… ``` Everything else — per-module detail, per-language conventions, architecture — lives in `.claude/rules/` and costs nothing until Claude opens the file it describes. Reference: [docs.claudectl.space/reference](https://docs.claudectl.space/reference/) for the block sentinels and generation behaviour, [docs.claudectl.space/memory](https://docs.claudectl.space/memory/) for the rules generator. ## Why Claude Code forgets your project between sessions, and what to do about it 2026-09-01 · claude-code, memory, context · https://claudectl.space/blog/why-claude-code-forgets-your-project Claude Code starts every session with an empty context window. Here is the actual mechanism behind the amnesia, and the three surfaces that fix it. ### The short answer Claude Code forgets your project because a session *is* the memory. Everything the model knows lives in one context window, and that window is discarded when the session ends. The transcript written to disk is an append-only log — nothing ever reads it back. The only knowledge that survives into the next session is whatever is loaded at startup: `CLAUDE.md`, path-scoped rules files, and anything a `SessionStart` hook injects. Fixing the amnesia means deliberately moving knowledge out of the conversation and onto those surfaces, on a budget, because everything on them is paid for on every message. That last clause is the part most people skip, and it is why the naive fix fails. ### What is actually on disk Every Claude Code session becomes a file: ``` ~/.claude/projects//.jsonl ``` One JSON object per line — user messages, assistant messages, tool calls, tool results. On a long-running project these get large; a 2,787-message session is an ordinary size for a week of work, and files past 100 MB exist. The folder name is a lossy encoding of the project path (`D:\Projects\my-app` becomes `D--Projects-my-app`), which is why the reliable way to map a folder back to a real project is not to decode the name but to read the `cwd` field that every transcript line already carries. The important property of that file is negative: **nothing reads it**. It is a record, not a store. Claude Code does not consult yesterday's transcript before answering today's question, and `/resume` does not change that — it reattaches you to one specific conversation in the current directory, which is a different thing from remembering the project. So the model's knowledge of your codebase has exactly one lifetime: the session. Two events end it early. **The context window fills.** `/compact` summarises the conversation to make room. It preserves the thread and drops the detail, which is precisely the material you built up over two hours of exploration. After a compact you are working with a model that knows the shape of the task and has lost the specifics. **The session ends.** New session, empty window, and the reasoning you did yesterday exists only as prose in a file nobody reads. ### The three things that survive | Surface | When it loads | What it costs | |---|---|---| | `CLAUDE.md` (project, user, `.claude/`, local) | Every message | Its full size, per turn, forever | | `.claude/rules/*.md` with a `globs:` header | Only when Claude touches a matching path | Nothing until then | | `SessionStart` / `UserPromptSubmit` hook output | Session start, or per prompt | Whatever the hook injects | That table is the whole design space. Anything you want Claude to know next Tuesday has to arrive through one of those three doors. ### Why "just write a bigger CLAUDE.md" stops working `CLAUDE.md` is read on every turn. Not every session — every *message*. A 4,000-token file is 4,000 tokens on turn one and on turn two hundred. That is fine at 40 lines and stable conventions; a hand-written `CLAUDE.md` is genuinely the right tool for a small project, and if yours is short and stays short you do not need anything else. It degrades with size, and it degrades in a specific way: the usual failure is not that the file is wrong, it is that the file is too big to justify and too tedious to prune. So it keeps growing, the per-turn tax keeps rising, and eventually you are paying for a paragraph about a module you deleted in March. The comparison is honest in both directions — [archeus's own compare page](https://docs.claudectl.space/compare/) says plainly that a 40-line `CLAUDE.md` needs no replacement. The scalable shape is different: a small always-on index, plus detail that costs nothing until it becomes relevant. ### Knowledge that lives outside the transcript archeus's answer is a semantic memory graph stored per project at `.archeus/memory/graph.json`. Modules and repos are summarised (incrementally, keyed by file hash, so unchanged code is never re-analysed) and merged with the **real** dependency graph extracted from the source — cross-module edges and an importance rank, not a guess from filenames. Two properties matter more than the extraction: **It is bounded.** Duplicate entities merge across modules, and a global importance cap (`memory_max_entities`, default 500) evicts the least-connected. The always-on cost stays flat while accuracy rises. That is the inversion that makes this work at all: memory gets *leaner and sharper* as the project grows, instead of heavier. **Nothing shrinks without a way back.** Any entity or lesson can be pinned so the cap can never evict it — pin more than the cap allows and the pins win, the cap gives way. A section of `CLAUDE.md` can be fenced between `` and ``, and AI compression will not even *send* it to the model, so it cannot be reworded or dropped. Pruning names the exact entries it will remove before removing them. Every replacement is snapshotted — twelve versions of `CLAUDE.md` and of the graph, browsable with a diff and restorable, and restoring is itself snapshotted so you can walk back out again. That graph then reaches a session through the three surfaces above: | Surface | What Claude sees | Cost | |---|---|---| | CLAUDE.md micro-index | Repo one-liners, module names, a recall pointer | ≤250 tokens, every session | | `.claude/rules/archeus-mem-*.md` | Per-module entities and relations, `globs:`-scoped | **0 until Claude touches those files** | | `UserPromptSubmit` hook (opt-in) | The subgraph relevant to *this prompt* | ≤600 tokens/prompt, under a second, local | The token arithmetic behind that split is a separate topic — see [how to cut Claude Code token costs](/blog/cut-claude-code-token-costs) for the audit and the numbers. ### Retrieval, and being honest about it The per-prompt surface only works if retrieval is good. archeus's recall engine runs four local rankers — BM25 over entity names and summaries, path and module match, dependency rank, and a confidence-weighted lesson signal — combined with Reciprocal Rank Fusion, which uses each ranker's *position* rather than its score, so nothing needs calibrating. No embeddings, deterministic, under half a second on 500 entities. You can call it yourself: ``` archeus recall "the memory graph consolidation cap" ``` Claude can call it too, mid-session, through Bash — which is the cheapest possible form of on-demand memory, because it costs nothing until something asks. Two lessons from building it are worth stealing regardless of the tool you use. First, a lexical ranker needs a stopword floor: without one, the query `the` returned 33 entities on this repo's own graph, and the prompt hook cheerfully injected memory into prompts that asked for none. IDF alone never reaches zero. Second, a budget should *fit* rather than truncate — one verbose top hit should not discard every smaller fact behind it. And the honest limit, stated in [the memory docs](https://docs.claudectl.space/memory/) rather than hidden: this is lexical retrieval. A query that shares no vocabulary with the stored summary will miss, and no weight tuning changes that. ### Facts that stop being true A memory that only accumulates is a memory that goes stale. When you migrate Flask to FastAPI, "this project uses Flask" is not wrong-forever, it is wrong-from-a-date. archeus invalidates superseded facts with a timestamp instead of deleting them: the old fact is kept as history and never injected again. The graph tracks what is true *now* and what changed, which is a materially different data model from a pile of notes. Alongside that, entities recalled often gain weight and survive consolidation; knowledge nobody touches fades, access-based, like a forgetting curve. ### Learning from finished sessions The transcript nobody reads does contain something worth keeping: the moment you told Claude its fix was wrong, and the fix that actually worked. After each session archeus distils durable **lessons** — error→fix pairs, decisions, preferences — from the transcript. High-confidence ones auto-approve; the rest wait in a review screen. Approved lessons boost recall and decay if never used again. The load-bearing detail is that this runs at `SessionEnd`, not `Stop`. `Stop` fires on every turn, and binding a whole-transcript scan to it means re-streaming a growing file dozens of times per session for a heuristic that only needs to run once. ### What to actually do 1. **Install it and build the graph.** `pipx install archeus`, open the project, press `m` for the memory hub, build. Nothing on disk moves. 2. **Turn on the rules surface.** Per-module knowledge in `.claude/rules/` costs zero until Claude opens a matching file. This is the single highest-value change. 3. **Add the recall hook only if you want per-prompt injection.** It is opt-in, budgeted at ~600 tokens, and runs locally. 4. **Turn on recent-work memory** for a token-free one-line summary per session, injected as a compact digest on the next `SessionStart` — so a new session knows what the last few did. 5. **After a compact, hand off instead of re-explaining.** **Hand off** on the session row (or `⇧K` in the terminal UI) starts a *new* session seeded with the previous transcript, written to `.archeus/injected-context.md` and passed as a pointer, not a paste. ### Where this does not help Memory extraction is Claude calls — routed to a cheap model and run rarely, but not free. The saving is on the per-message context you stop paying for, and if your project context is already 40 lines there is nothing to save. Retrieval is lexical, so an oddly-worded prompt will miss. And none of this makes Claude Code remember on its own; it makes the next session start informed, which is the achievable version of the same thing. Reference detail lives at [docs.claudectl.space/memory](https://docs.claudectl.space/memory/). --- # Changelog All notable changes to this project are documented here. The format follows Keep a Changelog, and this project adheres to Semantic Versioning. [2.8.1] - 2026-10-16 Fixed - Remote Control is available in new and resumed Claude sessions. Sessions started or resumed from archeus launched with CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC set, which makes Claude Code's Remote Control unavailable. Hand-off never set it, so the option only appeared there. archeus no longer sets it. - archeus's own Codex calls no longer appear as sessions. Memory builds and lesson runs on Codex were listed in the project's session list next to your own sessions, and counted in session topics and the worklog. They are now hidden, as they already were for Claude Code. [2.8.0] - 2026-09-25 Added - Choose which CLI and model archeus uses for its own calls. Memory extraction, lessons and the CLAUDE.md, agent and skill generators used to run on claude -p and nothing else. The Economy model card in Settings, and the TUI settings menu, now pick the CLI too: Claude Code or pi, with the model named the way pi names it (provider/model), local models included. On auto, archeus uses Claude Code when it is installed and pi when it is not, so a machine with only pi builds memory without configuring anything. pi runs these calls with read-only tools and without saving a session, so they do not show up in your pi session list. Fixed - Resuming a pi session works. One-click resume sent Claude Code's default model to every session, and pi refused to open one with Model "claude-opus-5" is ambiguous across providers. A resumed pi or Codex session now reopens on the model and thinking level it recorded, and a Claude model id or effort level that another CLI cannot use is never passed to it. - Building memory with only pi installed. It failed with "the claude executable was not found". It now runs on pi (see Added). - Setting up a project no longer fails at the last step. The job built memory and then crashed on TypeError: object of type 'int' has no len() while reporting how many entities it had found. - Hand-off works with pi. Both the session-row button and the TUI listed only Claude accounts as targets and always started claude. They now list every installed CLI, put the source session's own CLI first, and start pi with the conversation written to .archeus/injected-context.md and a first message pointing at it. [2.7.0] - 2026-09-23 Added - Install a plugin you can name. The Plugins page had no equivalent of /plugin install : the only install buttons were for a plugin another account already had. There is one now, completing name@marketplace from what is on screen, and it registers on every account the way adding a marketplace already did. - A Recommended card with a short starting set. A fresh account has an official marketplace with hundreds of plugins in it and nothing saying which few are worth having. The card names six, with one line each on why, and offers to register the marketplace first for a recommendation that comes from one you do not have yet. - The Plugins page works under Codex. It was read-only there, which turned out to be a vocabulary gap rather than a real one: Codex says plugin add, plugin remove and plugin marketplace upgrade where Claude Code says install, uninstall and update. archeus keeps one table of those verbs now. The only button missing under Codex is updating a single installed plugin, because Codex has no such command. Changed - The marketplace button says Update, not Refresh. It always fetched every registered marketplace from its source — which is what moves a plugin's available version — and "Refresh" described a re-read of what was already on disk. It also acts on the account the page is showing rather than whichever one happened to be active. - Uninstalling a plugin says what it actually removes. The old wording — "Anything it contributed goes with it" — read as a threat to the code the plugin had written in your projects. It now counts the plugin's own skills, agents, commands and hooks, names the account, and says plainly that your own files are untouched. Fixed - The "update available" badges belonged to the wrong account. Both halves of that comparison are per-login — the installs and the marketplace clones — and the page asked for them without naming one, so it showed the default account's answer whenever another account was selected. - A new model release no longer breaks the launch window. When Opus 5.5 and Fable 5.1 reached the model list, the quick-start presets, the Power slider and Adjust power went on naming Opus 5 and Fable 5. Picking the Deep reasoning preset selected no model, and pinning Opus 5.5 left the preset's model printed under the Power bar. The presets and slider stops now follow the newest model in each family, the advice covers new versions, and the readout under the slider shows the model you pinned. [2.6.0] - 2026-09-17 Changed - A project opens on four tab groups instead of nine tabs, and each group leads with the tab it is opened for. Session, then Context (Memory, CLAUDE.md, Audit), Project (Tools, Usage, Repos) and Actions (Plan → Execute, Code Review). Review is called Code Review, which is what it does. Coming back to a group keeps the sub-tab you were last on, so the order decides the first visit only — which is the visit where guessing wrong costs a click. - The pages lead with what people came for. Tools is two columns the page decides: on the left what the project launches with (its agents, its extra directories, its extra PATH), on the right what it has been doing (most-run commands, what to work on). Memory opens on what archeus knows, then on what it learned. The read-only diagnostics — project health and Claude Code's own record of the project — moved to Audit, where the rest of them already were, and the Architecture card went with the Graph button in the tab strip, which was already the door people used. - The apex site's header carries the donation link. A warm caramel Buy me a Coffee button — a plain link, never a Ko-fi widget, whose embed would drag cookie consent back onto a site that has none — and the only control on the site that is not blue. /support keeps the explanation and has dropped its own duplicate call to action. Added - CLAUDE.md is editable from the GUI, and only the half you wrote. The project's CLAUDE.md tab opens on a Your prose box holding the manual block and nothing else: everything archeus generates is left out of it and put back untouched on save, so the editor cannot overwrite a generated block even by accident. Fixed - The Memory tab left a whole column empty beside its first card. A pile balances its cards across columns, but a card that takes the full width ends the balanced run above it — and the lessons card took the full width because its table has five columns. With it second on the page, the run above held one card and filled one column of two, leaving 828x770px of background beside it. A pile no longer gives a wide table the full width: the same table reads fine in an 828px column, and the page is 1489px tall instead of 2187. tools/shot_gui.py now fails a pile run that cannot fill its columns, and measures horizontal emptiness as well as vertical — every check it had measured height, so a card twice as wide as its contents was reported as clean. - The settings pages were mostly background on a wide window. The annotated form puts an explanation on the left and the controls on the right, but the explanation was capped at 300px however wide the window got — so a ten-line description ran down a ribbon with 1225px empty beside it, and 1889px at 2560. The form is capped at a width it can use and the explanation gets a reading measure. The five version cards on Updates were full-width blocks holding about 500px of content each and are now a grid like every other set of independent cards, and the sentence explaining install mode is beside the value instead of 165px wide under it. - The home page said the same thing twice. The hero printed the site's one-line intro and the first station's lead one under the other, in two sizes; they are the same sentence with twenty more words on the end. It prints the longer one. - The longest prose on the site was 102 characters a line. The zigzag layout — /support, /changelog, /contributing, /code of conduct and every blog post — set its copy column to 54rem while the four other layouts held theirs between 44 and 52, and body paragraphs on the legal pages had no cap at all: they ran the full column width under a lead that stopped 114px short of them, so the two ended on different right edges. One measure now, about 88 characters, and tools/audit_site.py measures every paragraph on both sites at desktop width and fails past 96. - A numeric column's header did not stand over its own numbers. text-align:right had only ever been written on the cells, so tokens sat at the left of a column whose figures ended 250px to its right — on the context audit, both usage tables, the lessons table, disk and rotation. The header takes its alignment from its cells now rather than from whatever the renderer remembered to type. The same pass gives the narrow-window stacked layout its labels, and it had never run on a table filled by a fetch after the page was painted, which was most of them. - The site header scrolled sideways between 768 and 895px. Six nav links are 627px wide, which is more header than a tablet has once the wordmark, Docs and GitHub are beside them; the links now appear at lg rather than md, and below that they are in the disclosure menu they were always in on a phone. tools/audit_site.py checks 768x1024 as well as 390x844 now — a phone gets the menu and a desktop has the room, so the tablet band is the one width a phone-only probe could never see. [2.5.0] - 2026-09-16 Added - Automatic account rotation. When the account in use fills its 5-hour or weekly window, the next one with headroom takes over — archeus's own Claude calls, scheduled loops and new sessions all start on it. Three modes (Accounts → Account rotation): off, semi-automatic (the default — new work moves by itself, and a session you are in is offered the move) and fully automatic (the successor session opens on its own), plus a switch-away threshold and a per-login opt-out. Every switch is recorded in the Logs. It rotates by launching the unmodified claude binary under each account's own CLAUDE_CONFIG_DIR and never reads, stores or refreshes a credential, so it cannot log an account out of Claude Code. The successor resumes the conversation itself (--resume on the transcript's absolute path, forked), rather than re-typing a summary of it. Codex and pi are deliberately not rotated: their windows are not Anthropic's and there is no headroom figure to compare. Two things watch a session you are sitting in, which is the one case archeus is not itself present for. The status line reads this account's own 5-hour and weekly percentages off Claude Code on every turn, so the row says 96% — continue on work2 as soon as one crosses the switch threshold — before the window is spent, rather than after a turn has already been refused — and in fully automatic it opens the successor itself. A StopFailure hook catches the turn that does die on a limit: it records the refusal where every archeus process can read it, so a running GUI and archeus's own Claude calls stop choosing that account too, and then applies your mode. The hook is installed and removed by the mode setting, and the rotation card says which logins carry it. An offer is not repeated for ten minutes per session, because both triggers fire repeatedly by nature. One subtlety worth naming, because it made the pre-emptive half inert on a real machine: elect() is deliberately STICKY — it keeps the account in hand until that account is spent — and it decides that from the usage poller's cache, which a status line process has never filled. So it answered with the very account it was being asked to leave, and the row read "no account with headroom" beside five idle logins. Leaving is now a separate question (rotate.next_account) from starting, which is what the two callers that have already decided to leave actually mean. - A guided tour, in the app and on the web. Two tours of the same product: the first five minutes, ending with a session launched, and every function archeus has with what each one is for. In the desktop app it is a coach-mark that opens the screen it is describing and rings it — never a dialog over the thing it explains — reached from ? → Guided tour. In the terminal UI it is a screen of its own, Getting started on the main menu, which prints the key that reaches each thing from there. And on the apex, at /getting-started, it is walkable with the screenshots for anyone who has not installed anything yet. The steps are written once, in claude_sessions/tour.py, and tools/gen_tour.py carries them to the website — a product with three interfaces and three explanations of itself has three products. What the narrative is NOT is a copy of the tables that already declare every screen: a blurb says what a screen shows, a step has to say when you would reach for it. tests/test_tour.py fails when a step names a page, tab or manual page that does not exist, and when a screen the app has appears in no step at all — which is how a feature ends up being discovered two years after it shipped. - The tour records itself. py tools/capture_tour.py drives the real app through the real steps in a real browser and writes an animated WebP; py tools/shot_tui.py --tour does the same for the terminal. Both publish to the manual and the apex. It is a generated artefact like every screenshot, so it is re-recordable with one command when a screen moves, rather than a video that quietly stops matching the product. - Live sessions on the dashboard. One row per session being worked in right now — the project, who is on it, what it is blocked on this second, and the shape of its recent activity as a strip of coloured ticks, with a click through to the full flow graph. It rides the dashboard's existing poll rather than opening a second one, and "live" is decided in one place, so the count and the list cannot disagree. Deliberately a list and not several graphs on one canvas: zoetrope was sent exactly that and declined it, because several trees do not fit at a readable size and a fleet of tailers pays for the parse continuously. - Session flow graph — open any session as a live flow graph (Flow, beside Transcript in the session detail pane): prompts, model turns, tool calls, results, errors and subagent lanes on the session's own clock, with play/pause, speed, scrub, wheel-zoom and a click-to-inspect panel. Idle gaps are clamped, so a twelve-hour session replays in seconds, and Follow appends a session that is still running. Claude Code, Codex and pi. Behaviour adopted from zoetrope (MIT). - Four policy pages on the apex, and an operator who is named. /legal/privacy, /legal/terms, /legal/cookies and /legal/refunds, prerendered from one data table and wired from it into the footer, the sitemap, /llms.txt and /llms-full.txt — the manual links to those copies rather than carrying a second set, because two copies of a policy is two policies and the second one is wrong. - A support page on the apex, at /support. "Why is there a donate button on a free MIT project" is a fair question and a footer link cannot answer it, so the footer now points at a page that does: what a donation buys (nothing — no tier, no perk, no sponsor-only build), what it does not change, and the ways of helping that cost nothing. It does not restate the refund policy; it links to it. Still an ordinary hyperlink out to Ko-fi — no widget, no iframe, no script — so nothing of theirs loads until you follow it, which is what keeps the cookie policy able to say there is nothing to consent to. - Ko-fi, on six surfaces, from one handle. A badge and a License line in the README, .github/FUNDING.yml (which is what turns on GitHub's Sponsor button), a footer link, and funding metadata in pyproject.toml, the npm package and the gemspec. No widget and no Ko-fi JavaScript — an embed would have dragged cookie consent back in, which is exactly the surface those policy pages keep closed. - Memory has an episodic layer, and a quiet project now forgets. Alongside the facts it already kept, memory records what each session was like — its tool errors, the last error text, an outcome, the duration, the branch and the tokens — all read in the pass that was already running, so nothing here spends a token. Retrieval is what makes it worth having: score_episodes reuses the existing BM25 and IDF to answer "have I done this before", ranks a session that ended badly slightly higher because the dead end is the useful one, and renders LAST inside the same 600-token budget, so it is the first thing dropped when the prompt is already rich in facts. The ring holds 50 with a 60-day TTL. Forgetting used to hang off a refresh — consolidation, lesson decay and invalidation all ran only when something had changed, which is backwards, because facts go stale precisely when nothing is happening. forget_pass runs on the scheduler's quiet branch instead: episodes and lessons past their TTL, invalidations older than 30 days, and a staleness flag on anything not re-confirmed in 90 days. Staleness is flagged and counted, never evicted — a heuristic that quietly reshapes what recall returns is how a memory system starts lying confidently. Two retrieval defects went with it. Entities merged on name alone, so a workspace holding several repos that each legitimately contain a Claude Code or a memory system merged them across repositories — and the merge SUMS rank, so each fused entity outranked every true fact. Merging is scoped by repo now. And where two descriptions disagreed, the loser was silently overwritten by string length; a contradiction auto-supersedes where the evidence is temporal and is recorded where it is not. - Write a hook by hand, name it, and file it. Installing a ready-made template, asking Claude to generate one, or hand-editing Claude Code's settings.json were the three ways in; writing one yourself is the fourth. A hook can also carry a name and a category, which live in archeus's own settings and never in Claude Code's — that file belongs to another program, and an unrecognised key in a hook entry risks its schema refusing the lot. The identity is (event, commands), the same one the account provisioner already uses to decide whether two accounts hold the same hook. Changed - Removed duplicated definitions across the TUI, the scanners and the tooling: one display-width rule, one hidden-character class, one convention-overlap threshold, one snapshot reader, one render gate. No behaviour change. - The event log no longer lets one repeated warning evict every real error. Measured on a real log: 1,589 events, of which 1,270 were a single slow-endpoint warning and 79 were claude exited 1 failures the ring had already pushed out. The dedupe window stops a burst; nothing stopped a steady drip over two weeks. Rotation now thins what it keeps — at most 40 lines of any one (source, shape), newest first — which on that log keeps every quota warning and every subprocess error at half the bytes. Applied only when the log rotates, so the common append is still one getsize and no rewrite. - Three controls are identifiable as controls. The apex's secondary CTA, the copy button and the mobile menu summary were outlined in --color-line at 1.36:1 — under the 3:1 that SC 1.4.11 asks of a control boundary, and on the secondary CTA the only thing separating it from body text. They now use a 3.28:1 border; decorative hairlines are unchanged, the distinction being whether the boundary does identifying work. - /about no longer publishes figures nobody measured. A stat tile that had no number rendered as GitHub stars —, which reads as a claim of none rather than as "not counted"; a tile now survives only if its value carries a digit, and the disclosure line says when the figures were taken. Fixed - Half of the rate-limit guard had never worked. quota's 429 pattern contained two literal backspace bytes where \b had been meant, so it could never match; a 429 was only ever caught when the text happened to carry another marker as well. It now matches a status code ("apiErrorStatus":429, status: 429) without matching a number in prose. - tools/inspect_cluster.py inspected a hardcoded checkout path rather than the tree it runs in, so in any other worktree it graded a copy — which is what its own docstring says it exists to avoid. - A link gate could not tell a trailing slash from a path separator. test_an_apex_link_has_no_trailing_slash backtracked to the first slash it could end on, so a correct two-level apex URL was reported as a defect. Until the policy pages landed no tracked file named one, so that half of the pattern had never been exercised. - npx archeus did nothing at all (npm package 2.4.1; the Python package is unchanged). Under npx, npm puts its own generated shim on PATH under the name the launcher then looks up, so it found itself: on Windows that shim is a .cmd, which Node will not execute without a shell, and the failure surfaced as exit 1 with nothing printed; on macOS and Linux it is a symlink back to the same file, which would have re-entered it forever. The launcher now keeps a real console script — an .exe on Windows, anything not resolving inside node_modules elsewhere — and falls through to running archeus as a module when the only match is npm's. [2.4.0] - 2026-09-15 Added - A Harnesses page, and a sidebar about the workspace rather than about one CLI. Five screens only Claude Code has — Accounts, its own client state, output styles, subagents and hooks — moved out of the sidebar and behind a tab for that CLI, beside a new Setup screen per harness: whether it is installed, where, which version, whether an update is waiting, and its full capability table with the reason beside every gap. The pages themselves are unchanged; only which door they sit behind moved. Five greyed rows teach you the app is about one tool. - Usage & cost reads every CLI. A harness strip over the page, and the split is real: daily tokens and per-project spend are counted out of each CLI's own transcripts, which archeus already parses. Only the plan-window rail is Anthropic's, and it greys with its reason instead of switching the whole page off — which is what the old usage capability did for Codex and pi, wrongly. The dashboard's spend today names which CLI spent it when more than one did. - Models and quick-start presets for Codex and pi. Both were an empty box on a fresh install, because the model list was read only from what the CLI had already run. It now also reads the catalogue each CLI ships — pi installs a 1,354-model provider list covering its own models and Codex's — so the picker has suggestions before the first session, labelled run here versus ships with this CLI. Three presets per CLI over its own two scales, and a preset naming a model the catalogue does not publish is dropped rather than offered. - Non-Anthropic sessions are priced. That same catalogue carries per-model prices, so a gpt-5.5 or Gemini session shows a cost instead of n/a. A model nothing publishes a price for stays unpriced — no guess, because an Opus-tier fallback on a gpt-5.6-luna session would be wrong by a factor of fifty. - codex.exe and pi paths in ⚙ Settings → Paths & limits, beside claude.exe, for an install the automatic search cannot reach. Changed - Three capabilities Codex was declared not to have, it has. Checked against the installed binary rather than assumed: codex mcp list/get/add/remove is a full MCP surface, codex plugin list reads every marketplace, and codex doctor reports the installed version and whether a newer one exists — all three offline and without a login. The MCP page, the Plugins page and the Updates page answer for Codex now; plugins are read-only there, because installing goes through a marketplace resolver archeus does not own. codex update and pi update are buttons. - Hooks stay unavailable for Codex, with the real reason. Its hook contract is byte-identical to Claude Code's — the binary carries the same session_id, transcript_path, hook_event_name and stop_hook_active payload fields — but every handler in hooks.json is gated on an undocumented trusted_hash whose mismatch is refused silently. A hook archeus installs that never fires and never says so is worse than no hook. - Auto-memory says where its two deliveries land. The digest already reached every installed CLI's instructions file; the path-scoped .claude/rules/*.md half only ever reached Claude Code, and nothing said so. That is a declared capability now, the Memory tab names which CLIs read those files, and the CLAUDE.md map lists every instructions file a project loads rather than only Claude Code's. Fixed - Installing a second CLI no longer reports the workspace as broken. The dashboard's wiring ring counted hooks and a statusline over every CLI home, and neither is something Codex or pi has — so a fully-wired machine dropped from 1/1 to 1/3 the moment a second CLI appeared. - A Codex or pi project is no longer attributed to an account called .codex. The spend breakdown resolved account names from Claude Code's account list alone. - Structured data on every page of the manual, derived from the page. All twenty-nine pages now carry a TechArticle and a BreadcrumbList, under the same author, website and application @ids the marketing site uses — so the two hosts describe one project rather than two that share a name. Troubleshooting additionally emits a FAQPage and Quickstart a HowTo, both built from the page's own headings at build time rather than written beside it: the questions an answer engine quotes are the exact symptom strings and error messages the page already displays. Previously one page in twenty-nine opted into any structured data at all. - tools/optimize_images.py, tools/check_site_seo.py and tools/audit_site.py. Respectively: every published image at the size it should be; the built HTML checked for titles, canonicals, descriptions, social cards, JSON-LD and image dimensions; and every page loaded at 390x844 with a failure on anything past the right edge. The first two run in CI on every push, the third on the weekly schedule. - tools/set_domain.py — moves both published sites to a new domain in one command, with notes/domain-change.md for the parts no script can do (DNS, the Vercel projects, GitHub Pages, the Search Console change of address, and the 301s that have to stay up for a year). notes/search-console.md covers verification. Changed - Both sites are about 10 MB lighter. The architecture-graph animation is written as animated WebP instead of GIF — the same 30 frames at 538 KB instead of 5770 KB — and it is the largest paint on two pages. The manual's screenshots get a WebP derivative where it beats the PNG (Desktop app went from 1470 KB of images to 227 KB); the PNG stays as the master, because that is what the README embeds and what PyPI renders. The docs header logo was the 1254px master at 998 KB on every page and is now a 256px derivative; the favicon carried a 256px frame and now stops at 48. - Images in the manual declare their intrinsic size and load lazily, so the text under them no longer moves when they arrive. - Explicit crawler rules on both hosts, including Google-Extended and Applebot-Extended — the two agents for which "no rule" reads as a refusal rather than as the default. Preview deployments are excluded from indexing. - HSTS, X-Content-Type-Options and Referrer-Policy on both sites. Not preload, deliberately: that list is effectively permanent and a domain move is pending. - Nineteen pages had a meta description longer than a search result renders; all are rewritten to fit. docs/api.md had none at all and fell back to the site-wide one. - The FAQ page's twenty-three questions are headings now. The page emitted a twenty-three entry FAQPage while the only headings in its markup were the four in the footer. - PyPI can filter this package by Python version: the classifiers named 3 and no minor. Fixed - Seventeen internal links redirected before they arrived. The apex serves /features and the manual serves /installation/; links written with the other site's convention cost a 308 each. Two links pointed at the pre-domain GitHub Pages host. - A .grid override made three pages of the manual scroll sideways on a phone. A bare minmax(20rem, 1fr) is a floor the track cannot go under; Material's own rule clamps it with min(100%, …) and this one did not carry that across. Found by measuring, not by looking. - app/apple-icon.png was built and routed but never linked: naming icons in the root metadata replaces Next's file-convention detection rather than adding to it. The web manifest pointed at an ICO for a size the file no longer contains. - archeus reads three coding CLIs, not one. OpenAI Codex and pi join Claude Code as first-class harnesses: their projects, sessions, previews, turn counts, models, token spend, search and usage all merge into the same lists, with no configuration — archeus looks for each binary and its home, and a CLI it cannot find is simply not offered. A provider is an endpoint and a harness is the binary itself; the two axes are orthogonal, and the launch picker shows both because they are both answers to "what runs this session". See More than one CLI. - One memory graph behind all three. A project's memory is the project's, not Claude Code's memory of the project, so the same digest is delivered into every instructions file an installed CLI reads — CLAUDE.md for Claude Code, AGENTS.md for Codex, and both for pi, which reads either. A machine with no Codex never grows an AGENTS.md. The agent routing table deliberately does not follow: delegation is a Claude Code capability, and writing "delegate with the Agent tool" into AGENTS.md would instruct Codex to use a tool it does not have. - Skills install into every CLI. SKILL.md is the Agent Skills standard and all three read the same file, so a personal skill lands in every account and every CLI, and a project skill writes .claude/skills and .agents/skills — two directories, not three, because .agents/skills is the cross-harness convention Codex and pi share. - New session opens on a tab strip. Which CLI or backend a session runs on used to be a chip row six fields down inside a collapsed "Advanced" panel, beside the thinking cap — the only control in that form that changed which tool ran. It is now the first question, because it decides what the rest of the form means: a Codex session has no worktree and no name-at-launch, a pi session has no permission mode, and those fields are removed with the reason stated rather than left as dead inputs. The default is Claude Code and is configurable in Settings → Defaults → Starts on. - A CLI can be switched off, and off means everywhere — the launch tabs, the project list, the sessions list, the usage table, and the instructions files that get a memory block. Nothing is uninstalled and nothing on disk is touched. Claude Code is listed and locked: it is the binary archeus itself runs for memory extraction, lessons and every other AI feature. Fixed - A one-shot run in the OS scratch directory is no longer a project. A codex exec or claude -p in %TEMP% writes the same session state a real project does, and the sidebar grew a tab for a directory that is gone by the next boot. - Codex sessions reported zero tokens, and search, usage and the dashboard reported no sessions at all. The corpus walk listed *.jsonl in the project folder, which is where Codex keeps an index rather than a transcript; it goes through the same two seams the sessions list already used. Token spend is read from the rollout's token_count events, whose cumulative totals are banked as deltas — summing one per turn multiplies a session's spend by its turn count. - Run a session against a local model, OpenRouter or a self-hosted server, not only OmniRoute. archeus could already point a real claude session at another backend — that is what the OmniRoute support has always been — but the capability was wired to one product name, so an Ollama, vLLM or llama.cpp server speaking the same protocol was unreachable. Settings → Model provider now takes any endpoint that serves POST /v1/messages. Sessions keep their agents, skills, hooks, MCP servers, slash commands and checkpoints, because none of those ever talk to the model API. See Model providers for the list of what a backend swap genuinely costs — subagents, prompt caching, extended thinking and web_search are affected, and three of the four cannot be fixed from outside Claude Code. - Every session picks its own backend. One backend at a time was a real constraint, not a screen limitation: the failover and gateway proxies re-read the settings on every request, so two live backends would have handed one session's credential to the other's upstream. Backends are now named profiles — Settings → Models lists them, each with its own URL, key, model, context window, translating gateway and failover list — and each one owns its own pair of ports, so its proxies are pinned to it at spawn and can no longer resolve to a neighbour. The launch modal and the terminal picker both offer the list, so one project can run on a local vLLM while the next runs on OmniRoute and a third on your Anthropic account. Your existing backend is carried across into a profile on first start, keeping its key, URL, model and failover list; nothing is lost and nothing needs re-entering. - Run archeus's own Claude calls on the configured provider too. Memory extraction, lesson distillation, code review and the CLAUDE.md / agent / skill / hook / system-prompt generators always went to Anthropic, whatever the provider card said — they are the cheapest, highest-volume calls archeus makes and the best fit for a local model. Opt in with Run archeus's own calls here too; off by default, because these run unattended and moving them changes which account is billed. An unreachable backend fails the call rather than quietly falling back to the account you routed away from. Fixed - The upgrade from the previous name is finished. The rename moved everything that was a path and nothing that was not, and the one repair it did ship ran exactly once — at the one moment when nothing was broken yet. Four things came out of that, and every one of them was measured on a real machine rather than reasoned about: - Hooks and the statusline are repaired on every start, not once during the migration. They record an absolute path into the environment that installed them, and everything that kills such a path — uninstalling the old package from its own pipx venv, moving or re-cloning a checkout, rebuilding a virtualenv — happens after the migration has run and closed its flag. Only a path that no longer exists and names one of archeus's own scripts is rewritten; a hook you wrote by hand, or a fork running from a checkout, is left alone. The statusline is rebuilt with the windowless interpreter it was installed with, so the repair does not start flashing a console window once per conversation turn. - A duplicated memory block in CLAUDE.md is removed. The sentinel comments around the generated memory, agent-routing and loop blocks carry the tool's name, so after the rename nothing could find the old ones and every build appended a second block beside the first — which then went into every session, for ever, saying whatever it said the day the rename landed. A block the new name has since rewritten is dropped; a block with no successor is renamed in place, so the next build updates it instead of duplicating it too. A half-written pair is left alone, and the KEEP fence around your own prose is always renamed and never removed — until it was, the compression pass could not see it. - Scheduled loops are re-registered. A scheduler entry is a name, not a path, so the registry moved and the Task Scheduler / cron entry did not: the loop read as unscheduled in the UI while the old entry went on firing, into an interpreter that may since have been deleted. The old entry is removed and the loop re-registered, which rebuilds its command line at the same time. - CLAUDECTL_* environment variables and a plugin still installed under the old id are reported on startup. Both have been silently doing nothing since the rename, and neither is archeus's to edit: one lives in your shell profile, the other in caches that only the claude CLI may write. - CLAUDE.md is written atomically. The writer for all three machine-maintained blocks was the last one still using a plain truncating write, and Claude Code parses that file on every turn — a write that died partway left it half a file. - Adding a backend threw away the form it had just opened. A new profile has no id until it is saved — that is what keeps an abandoned Add from leaving anything behind — but the list redraw dropped any selection whose id was not in the list, which is every draft. The pane blanked the moment it appeared. - The model catalogue never loaded. Backends are per-profile now and the catalogue endpoint takes the profile id; the page was still asking for it without one, so the request was refused and the model picker was simply never built. The card offered a free-text box where it should have offered the live list. - Stopping the failover proxy did nothing at all. The button raised a TypeError on the background thread — it asked to stop "the" proxy, from before there was one per backend — where nothing logs it, so the job neither finished nor reported. The last call site missed in that conversion. - An image sent through the translating gateway is now reported. Anything that is not text, a tool call or a tool result had no branch in the translation and left the request in silence, so a vision model behind the gateway stopped seeing the picture with nothing said anywhere. It is still dropped — translating it is a larger change — but the proxy console now says so, once per kind of block, exactly as it already did for prompt caching. - Which backend a session ran on is written down at launch instead of guessed afterwards from the model ids in its transcript. That guess cannot tell an Anthropic model served through a provider from a direct run, and said so in its own source. A new session's id is archeus's to choose, so the answer is recorded before the first line is written; sessions started elsewhere still fall back to the guess. - Four generators bypassed the one headless-call helper. Authoring an agent, a skill or a system prompt, and analysing an MCP server, each rebuilt the same claude --print command by hand — so none of them honoured the --max-budget-usd cap, and all four passed the whole prompt as a command-line argument, which is the Windows length limit the shared helper exists to avoid. They call it now, and a test fails a fifth copy. - Subagents kept a model id a routed backend cannot resolve. The frontmatter-stripping that makes agents work on a non-Anthropic model was a parameter three of its four callers never passed, so agents synced from the GUI or accepted from a suggestion still carried model: claude-… and 401'd. It is derived from the active provider now instead of being asked of each caller. - A routed model's cost read as ~$0.00. There are no published rates for one, which is not the same as it being free — a paid OpenRouter or self-hosted model was reported as approximately nothing. It shows n/a; a session mixing Anthropic and routed models still quotes the part that is known. - Extended thinking no longer fails the whole turn on a non-Anthropic backend. Claude Code sends the adaptive-thinking field unconditionally and an upstream that does not know it answers 400, so it is disabled automatically whenever a provider is configured. Changed - The omniroute_base_url / omniroute_api_key / omniroute_exec_model settings are now provider_*, with provider_kind choosing between OmniRoute's managed daemon and a server you already run. Existing settings are migrated on first start. [2.3.0] - 2026-09-11 Changed - The banner is the gold ARCHEUS lockup, and so is the social card. It replaces the generated cyan wordmark everywhere it was used, and it is artwork now rather than output — tools/make_og_card.py no longer writes docs/assets/wordmark.png, it READS it, so the card and the README show the same file and neither can drift. Transparent, so it works on a light GitHub page, a dark one, and straight on the card's navy. The card loses its cyan archeus title with it: the mark carries the name, in the brand's own letterforms and twice the size, so the word was on the card twice. Fixed - "Update now" installs, with archeus still open. It had never worked on any path, for four separate reasons, and each one hid the next. - pip was run with no stdout. The worker is detached, so its output goes to a file handle — and a child that redirects nothing does not inherit one. pip came up with sys.stdout None and, under pythonw (which is what the desktop shell runs on), exited 1 having printed nothing at all, not even a traceback. The update log held the worker's own two lines and no pip output, which reads exactly like an install that never ran. The install is captured now, and the whole of pip's output goes to the log. - It waited five minutes and then installed anyway. The strip says "it installs when you close archeus" and a session lasts hours, so the cap fired first every time and pip met the locked console script it had been deferred to avoid. Measured: that install fails after uninstalling the old package, leaving nothing installed at all. The wait is unbounded, and a caller that passes a deadline gets "could not" rather than the unsafe install. - It never had to wait in the first place. A running .exe can be renamed, only not overwritten, so the worker moves the console script aside and pip writes a fresh one beside it — and puts the old one back if the install fails. Update now installs immediately; install on quit is the same worker told to wait for this process first. - A checkout called itself a stale pip install. One pip install -e . leaves archeus.egg-info in the repository for ever, and the checkout is first on sys.path, so importlib.metadata reported the working tree as an installed distribution at whatever version that file was built at — 2.1.0 for a 2.2.0 tree. The banner offered an upgrade, pip answered "Requirement already satisfied" about a copy the process was not running, and the restart showed the old version and offered it again. What archeus is running is decided by the layout on disk now, not by the record sitting next to it. The strip itself was the fourth fault: the click starts a job whose banner IS the update strip, and the finish path hides the strip it borrowed, so the message that followed — with its Restart now button — was painted into a hidden element. And the job used to report "done" the moment a background worker had been started, which is a different fact from an install that worked; it now holds until pip is finished and reports what pip said. - The app icon reaches installs without PyQt6 — which is most of them. The mark shipped inside the package and only the Qt window ever read it. PyQt6 is optional, so a plain pip install archeus opens the GUI as an Edge --app window, whose taskbar icon is the page's favicon, and the page served none. The server now serves /favicon.ico from the same file the Qt window uses. - A browser tab that abandons a request no longer prints a traceback. Closing the window, or navigating away while the page's fetches were still in flight, dumped fifteen lines of ConnectionAbortedError: [WinError 10053] into the console with nothing wrong — socketserver prints a full traceback for any exception escaping a handler, and the per-request log silencer does not cover it. Both local servers now share one rule for which exceptions are the user's problem: a peer that went away is dropped, anything else goes to the log and the Events screen. The failover proxy had the same hole, and its console window is the one users are told to read. - Auto-memory's interval is counted from the last pass, and the clock now survives the process. A pass ran a couple of seconds after every launch, so closing and reopening archeus five times in an hour bought five passes and spent five times the quota the schedule was supposed to cap. The last pass is recorded (a file mtime, so there is no state to corrupt), the first pass of a run waits out whatever is left of the interval, and the Auto-memory card shows when the next one is due. - Two paragraphs printed on top of each other on the Updates page. The annotation column of a settings card gave grid-row: 2 / span 20 to both the section's first paragraph and every .secthint below it — which is one grid cell, not a stack. The layout audit gained the check that sees it: two normal-flow children of a card whose boxes overlap, the opposite failure to the empty-floor measurement it already had. [2.2.0] - 2026-09-10 Changed - Every page now has a declared shape, and five shapes is all there are. A page's composition used to be whatever its renderer happened to emit, so the choice between a plain grid of cards and a balanced pile was made by hand, once per page, twenty-eight times — and nothing could tell a considered layout from an accident. The shape is now the sixth field of the table the page itself is declared in, and one function reads it, so a renderer emits its sections and never states, or gets to forget, what kind of page it is on. There were six shapes; the sixth was grid, the un-designed default. Every page has a real one now, so it is deleted rather than left as the place the next page lands by accident. - A list of one kind of thing is a list, with what you picked beside it. MCP servers, hooks, output styles, accounts, repos and sessions were pages of rows whose name sat at the left of the window and whose buttons sat at the right of it — on a wide display, a name, two thousand pixels of nothing, and then the controls. Each is now a list in its own scroller with a detail pane next to it. Three of them stop opening a drawer over the page you were reading to show you a detail: the MCP server's configuration, an output style's text, and a repo's worktrees are in the pane. - The two halves of a split are level. A detail pane sized to its own content sat as a 200px box beside a 900px list with the column under it simply dark — 616px of it on the MCP page, at every window width. Both halves are panes the height of the row now, the detail scrolls inside its own instead of growing the row past the window, and every split opens with a row already picked, so the pane is never an invitation next to a full list. - Output styles is one list, not five grids of the same object. Clicking a card used to write a settings.json — selecting a style and inspecting one were the same gesture. Picking a row now only picks it; putting a style in force, copying a starter and deleting are buttons in the pane. - The session list gives up its hover-only action strip. Ten actions appeared at the right edge of a row when the pointer crossed it. Resume stays on the row — resuming is one click, from the list, as it was — and the other ten are in the pane, on the session you picked, visible. - A page that is one homogeneous list gets the whole width. Logs, search, the code review, per-session usage and the context audit were single cards in a three-track grid, so the list you opened the page for got one column of three. - The settings pages stop flowing their controls into columns. Every design system says the same thing about a form and multicol was exactly the wrong shape for the five pages that are nothing but controls. Each section is now a single column of controls with its title and the sentence explaining it in a narrow column beside them, which is what the width on a wide display is for. A section's Save sits under the controls it saves rather than pinned to the far edge of a half-empty card, and it is secondary: six primary buttons down one page is six things all claiming to be the thing to press. - A number input is as wide as its value. A four-digit port had a 900px box, because the rule that gives every control the full width of its field is right for a path and wrong for a number. - A form section does not mix its save patterns. Nothing did, and the one place that nearly did is commented in the wiring — update checks and notifications write the moment you pick one, so they sit in a card with no Save at all. That is a gate now, read out of the wiring rather than from a list: a chip row that posts on pick, inside a card that also has a Save, fails the build. - Two fields share a row only when they are one value. Five pairs on the settings pages were two unrelated settings side by side to save vertical space — an editor path beside the Claude binary, the config dir beside a spend cap, two independent memory caps, two unrelated switches, a proxy port beside a window preference. Each is a control on its own row. The three that remain are one value each: an exporter's endpoint and protocol, a proxy's URL and key, a model and the effort it runs at. - An empty state is left-aligned where the content it replaces would have started. Centred with 7vh above it, it read as a page-level apology wherever it was really an in-card note — floating in the middle of a detail pane, or a third of the way down a card whose heading is at the top. It keeps the old treatment in the one place it earns it: written straight into the page with no card around it. - The app chrome stops claiming to be sticky. The header and the tab strip carried position:sticky and had nothing to stick to — they are siblings above the only thing that scrolls, so it did nothing. They stay positioned, because that is what makes their z-index mean anything. - A section whose body resolves to nothing is not painted, and neither is a table whose header stands over no rows. Reading the pages found eight of them. Emptiness that is itself the information keeps its card — "no MCP servers configured" is that page's whole subject — because an empty state is text, and the rule only removes what has nothing at all. [2.1.0] - 2026-09-09 Added - Every page now fits the space it has, at any window size. Every internal layout rule in the GUI decided its shape from the WINDOW, and no card is ever the width of the window: a card sits in a 560-760px column of the content grid at any window above 1280px. So the rules written to rescue narrow content — a table's row-stacking, the settings matrix, a page header's gauge, the three-across lists, the list+detail split, the dashboard's own layout — described a state nobody is ever in. On a 2048px window the Memory tab's lessons table was handed one 740px column, squeezed its status column to 23px, stacked approved onto its own tag, and left the other half of the row dark. Those rules now measure the box they are in rather than the screen, which also means dragging the sidebar re-lays the page out instead of moving the content edge by 240px with nothing noticing. A row of cards is also what leaves a hole in a page — a row is as tall as its tallest card, and the rest of it is empty. Pages that are a stack of independent sections (Help, Tools, CLAUDE.md, Plan → Execute, Memory, Output styles, Loops, Audit, the settings pages, the global CLAUDE.md) now balance their sections into columns instead, so a 147px section beside a 794px one costs nothing. A card holding something genuinely wide — a five-column table, the per-account settings matrix — takes the full width by itself, decided by what is in it rather than by a list somebody has to maintain. Sparse pages stretch to fill instead of leaving an empty column, and explanatory prose inside a card stops at a readable line length rather than running the width of a 2560px display. Measured with a new probe in tools/shot_gui.py that reports dead space rather than overflow — ragged rows, wasted grid cells, empty card floors, squeezed table columns, over-long lines — across 19 pages, 9 project tabs and 8 looks at three widths: 78 findings, down to none. It fails the run rather than printing, and counts what it measured, because a probe that stops finding anything reports success. Verified in the desktop shell as well as the test browser (tools/probe_layout_qt.py). - A build queue for project memory. Turn it on and archeus works through the modules that still owe an extraction while you are not using it, across every account rather than whichever one the process happened to start under, and stops when you tell it to. - Workspace status now checks whether your CLAUDE.md prose still agrees with the memory graph. CLAUDE.md has two halves and archeus owns exactly one: it rewrites the AUTOGEN, SESSIONS and memory blocks from live inputs, and it never touches the prose above them, because a tool that silently rewords what you wrote is worse than one that lets it age. The cost of that guarantee is that the hand-written half had no freshness signal of its own, and it is append-only by habit — a fact written near the top is rarely read again. The new claude_md_claims check reads the countable claims in your prose ("32 palettes", "4 worlds") and compares them against the same claims in the memory graph, which is re-extracted from the code: 🟡 CLAUDE.md says 29 palettes, memory says 32 It reports a disagreement, not a verdict. The graph is usually the fresher side, but it holds entities extracted in different cycles and can lag too, so the remedy names rebuilding memory first and the GUI button opens the file rather than pretending to repair it. There is deliberately no automatic fix. Conservative on purpose, because a noisy check gets switched off: a noun stated with two different numbers is ignored rather than guessed at, units (tokens, days, lines) never count, only plural nouns count, and a sentence in the past tense is skipped — "an earlier design was 26 renderers" is history, not a stale claim. A project with no memory graph is marked n/a and scores nothing either way. One JSON read, no model call and no subprocess. Changed - The graph world's cluster is drawn without a lighting model, and so are the architecture graph and the website. It keeps every part it had — the rod shell, the inner web on its own reversed clock, the hull faces, the coarse frame, the spokes, the beads, the junctions, the centre, the orbiters, the conduits — as fine additive lines and rings rather than lit, glassy solids, so all three surfaces represent one object instead of three approximations of it. It is also 24,494 triangles in 19 draw calls where it was 353,566 in 33, which is what fixed the stutter below. - Updating no longer opens a terminal. The installer has to wait for archeus to exit before it can replace the running files, and it used to do that in a console window that took the foreground and sat on "Waiting for archeus to exit…" — which reads as the update having hung. It runs detached and windowless now, writes to a log, and can bring archeus back up when it finishes. - "Open project by path" opens the project. It launched a session in that directory instead, which is a different and much larger action than the one the button names. Fixed - The graph world stuttered in the desktop shell, worst in full screen. Measured inside the real Qt window, the lit cluster pushed frame delivery to a 249.9ms 95th percentile with no long tasks at all, so Chromium halved the page's frame rate and the whole app lurched between 60 and ~17Hz. The flat cluster holds 16.8ms. Two sessions of measurements before it had been reading a PARKED page — the probe never took focus, and the app pauses everything when it loses focus by design — so the tool now takes the foreground properly and prints whether it had focus, rather than reporting zero frames beside a clean frame rate. - Eight places the app spoke to itself instead of to you. Found by reading every page's text rather than looking at it: the freshness rows on the Memory tab were the checks' own identifiers with their underscores swapped for spaces (claude md, claude md fresh — two rows whose names do not say how they differ), so each check has a written label now; the remedies beside them said "press m → b", which is the terminal's key path printed next to the GUI button that does the same thing; the account sync card printed a seven-column table header over the words "No accounts to compare"; the Audit tab's token column read ~?; the Loops command preview read every ? until you typed an interval; the Memory tab answered "when" as 2026-08-12T09:15:00Z in three places and as "10m ago" in four others; and the project's Usage tab printed raw token counts where the rest of the app writes 412.0k. - The dashboard was hiding accounts. The plan-usage rail was a horizontal scroller sized for three cards, so with five accounts configured two of them were simply not on the dashboard. It wraps now. - Four responsive rules that had never once applied. The narrow-window chrome and the icon rail's content gutters were declared before the rules they countermand, and a @media block adds no specificity — so at narrow widths the tab row never tightened, the metric strip never closed up and the big readouts never shrank. Ten declarations that restated their base rules character for character are gone with them. - Published numbers that had drifted since 1.8.2. The project dashboard still reported version 1.8.2, 1,367 tests, 183 Python files and 129 commits; the marketing site parses that page, so every number on it was stale too. The README's test badge said 1,706 — a third number, matching neither. The documentation and the site also advertised 29 palettes, 7 skins and 19 hook templates against a codebase holding 32, 8 and 31, and docs/hooks.md described only 17 of the 31 templates, missing the memory-freshness, failure-logging, notification and test-running families entirely. - The README test badge is generated now, not hand-typed. tools/gen_metrics.py already counted the tests for the dashboard; it writes the badge from that same number, so the two surfaces cannot disagree again. Refreshed by the existing weekly metrics workflow. tests/test_docs_numbers.py gates the counts that are derivable from this repository — palettes, skins, worlds, hook templates, plugin commands and bundled skills — across the README, every documentation page and the marketing site's copy. - gen_metrics.py emitted two dead links on every run: [Download](download.md) and [Changelog](changelog.md) name documentation pages that moved to the apex site. Someone had been hand-correcting the generated file; the generator overwrote it each time. [2.0.1] - 2026-09-08 Fixed - The desktop window had no icon for anyone who installed from PyPI. The .ico lived at the repository root, which package-data cannot reach, so it never entered the wheel — gui_qt._icon_path() returned '' and Qt drew its default. A dev checkout had an icon and nothing else did, which is why this survived so long: the machine it was written on always had one. The icon now lives inside the package, where _icon_path was already looking as its second candidate, and ships with it. Changed - One logo, everywhere. archeus had two marks — a navy-tile one for the terminal and an inverted bright-tile one for the desktop GUI — each drawn by its own script. Two marks for one product is two things to keep in step, and the answer to "which one is the logo" was "it depends where you are looking". There is now a single hand-drawn mark, kept as docs/assets/logo.png, and one generator that writes every icon from it: the app icon, the documentation favicon and the marketing-site favicon. tools/make_gui_icon.py is deleted. Two things tools/make_icon.py does to the source, both deliberate: it crops to the tile using the alpha channel, because the export carries a soft drop shadow that an icon must not bake in (every OS draws its own) and the artwork sits off-centre in its canvas; and it saves from the largest frame, because passing sizes alongside a small base silently keeps only 16×16. - The social card carries the mark, sized from the measured width of the text rather than a chosen number — the first attempt used a fixed 360px and landed on top of the tagline. It is written to both sites now. It used to be generated into the documentation site and copied into the marketing one by hand, and the two had already drifted apart by a kilobyte. [2.0.0] Changed - claudectl is now archeus. The name changes; nothing else in this release does. Two reasons, and the second is the one that mattered: An unrelated Rust project publishes a claudectl too, and it holds six of the eight organic search results for the name — partly because a Rust crate is granted three high-authority domains (crates.io, docs.rs, lib.rs) where a Python package is granted one. That is not a gap any amount of content closes. And the tool is outgrowing what the name says: reading and managing other agent harnesses' state is the next piece of work, and a tool named after one vendor's product cannot carry it. In Paracelsian alchemy the archeus is the organising principle that keeps a body coherent as it grows, which is the job here. Everything migrates on first run, and nothing is deleted. Settings, accounts, per-project launch defaults, the stats and model caches, the agent and skill libraries, and every project's .claudectl/ — its memory graph, snapshots, plans and logs — move to the new names in one pass. A destination that already exists is merged into rather than clobbered, and a step that fails leaves both copies in place and is retried on the next start rather than being written off. What this means for you: pip install archeus # the command is `archeus` now Installed hooks and the statusline need no attention: they were always recorded as a path to a script, never as the command name. Renamed alongside: the X-Claudectl GUI request header, the CLAUDECTL_* environment variables, ~/.claude/claudectl.json and its sibling caches, and /.claudectl/. Not yet renamed, on purpose: the documentation domain and the GitHub repository path, which still resolve under the old name until both moves happen. They are one substitution away. [1.9.1] - 2026-09-08 The last release published as claudectl. Its only change is a notice saying so — in the terminal UI's banner, in claudectl --help and on the PyPI page. Without it pip install -U claudectl would report "already up to date" forever, because from PyPI's side nothing had changed. [1.9.0] - 2026-09-04 Two things claudectl says about itself turned out not to be true, and both are now. It said a GUI job would never hang on a keyboard, and three of them did. It said the local API was guarded by three layers, and it had two — while the page carrying the key was handed to anyone who asked for it. This release is mostly that: the security sweep, the hangs, and a session list you can read. Security - Cross-site scripting in the desktop GUI, and it reached a shell. Every value that reached an inline event handler in web/app.js — seventy-nine interpolations — was escaped at the wrong layer. ${JSON.stringify(v)} inside onclick='…' escapes for JavaScript and nothing for HTML; '${jsq(v)}' inside on…="…" did the mirror image. One apostrophe closed the attribute and everything after it was parsed as new attributes. The values are not yours: a filename from a repository you cloned, an MCP server name out of a project's .mcp.json, a git branch, a skill directory, a plugin name. Injected script ran same-origin with the API token in scope, and /api/settings plus /api/open-editor turn that into an arbitrary command. One helper, hesc(), applies both layers in the right order and is now the only way a value reaches a handler; tests/test_inline_handlers.py fails the build if a bare JSON.stringify ever appears in markup again. It was also a plain bug: a suggestion containing "don't" broke the page. - The page that carries the API token gave it away. GET / was served on the Host check alone, and / is the response the per-run token is substituted into — so any process that could open a socket to the port could simply ask for it, which on Windows includes a process running as a different user, because loopback is not a user-identity boundary. / now takes the token in its query string exactly as /graph always has, the launcher puts it there, and the SPA drops it from the address bar on boot. - The guard now has the three layers it always claimed. gui._guard() implemented Host and the token; the documented middle layer — rejecting a cross-site fetch — existed only in the failover proxy. It is the layer that still holds if the token leaks, so it is the one that mattered here. It had to be written as an allowlist rather than the proxy's outright rejection, because the SPA's own fetch() sends those headers. - git clone ran an attacker's URL before anything reviewed it. Installing a skill from git passed the URL straight to git clone with no scheme check and no --, and ext::sh -c … is a real git transport that executes on clone. The review gate that shows you what will be installed runs after the checkout, so it never protected the fetch. Remote URLs are now validated in one place (proc.remote_url_ok) and the option list is terminated; the same applies to adding a plugin marketplace and merging a worktree branch. - Two endpoints deleted whatever they were told to. /api/agents/delete reached os.remove(body['file']) and /api/skills/remove reached shutil.rmtree(body['dir']) with no validation at all, so {"dir": "C:\\Users\\you"} was a recursive delete of your home directory. Both now require a path claudectl actually manages. target_cfgdir, which becomes CLAUDE_CONFIG_DIR for a spawned claude, is validated by the same account allowlist as cfgdir — it was skipped only because it is spelled differently. - Path traversal in output styles. read and delete joined the style name into a path raw, three functions away from a save that had always sanitised it: ?name=../../../../Users/you/Documents/notes read that file, and delete removed any .md on the volume. - A repository name could inject script into the architecture graph, which is served same-origin with the GUI and under the same policy. The graph's JSON payload is also now safe against U+2028/U+2029, which are JavaScript line terminators that ensure_ascii=False emitted raw. - .claudectl/ marks itself never-commit. claudectl writes it into your repositories, and it holds bash-log.txt — every Bash command Claude Code ran, which routinely includes export TOKEN=… and curl -H "Authorization: …" — plus injected-context.md, an entire transcript. It now seeds a .gitignore the first time it is created. claudectl.json, which holds your OmniRoute key, is written 0600. - /api/state echoed the OTEL headers value back to the page, and that setting is documented as carrying Authorization=Bearer . It is the one secret in the settings payload that never got omniroute_api_key's write-only treatment. It is reported as a boolean now, and the field in the settings page is write-only: blank means keep what is stored. - A non-ASCII byte in the X-Claudectl header raised inside hmac.compare_digest and printed a traceback instead of answering 403. Added - Logs, in both interfaces. A new ⚙ Logs screen in the TUI and Logs page in the GUI, over one append-only file at ~/.claude/claudectl-events.jsonl: claudectl's own headless Claude calls and why they failed, background job crashes, auto-memory scheduler passes, the failover proxy, and any state file it had to quarantine. Newest first, filterable by level and by text, capped at 256 KB. Until now those failures went nowhere at all. The claudectl logger carried a NullHandler unless CLAUDECTL_DEBUG was set — which is off for everyone — so around forty log.exception sites, including every background job crash and every faulted API handler, wrote to nothing. One logging handler now fans them all in; no per-turn path writes to the log, and no hook touches it. - Hand off — on every session row in the GUI, and ⇧K in the terminal UI. Start a new session seeded with a previous one's transcript, under any account you like. It replaces the Tools-tab "new chat with injected context" flow, where you had to pick the source session from a list; the row is the source now. - A standard skin — no chassis, system font, hairline borders. The plain one, for when the point is the content. 32 palettes, 8 skins. - headless_quota setting — prompt (default), auto or off. Decides what happens when claudectl wants to make one of its own Claude calls and the account's limit is already full. Fixed - A session row shows what the session is about again. The action strip was invisible until you hovered a row, but it still occupied its full width at all times — so adding Hand off as a tenth action took roughly a quarter of the title away from every row, whether you were reading or acting. The strip is out of the layout entirely now: the title gets the whole row at rest, the actions fade in over its right end on hover, and nothing moves. They are reachable by keyboard for the first time. The row also carries the full title as a tooltip, the preview text kept behind it is no longer clipped to 65 characters, and an archived row shows its AI title instead of falling back to the preview. In the terminal UI the name column scales with the window instead of a fixed 30 columns, and the hint bar drops whole hints rather than cutting the last one mid-word — which is what ⇧K hand off had been doing at 80 columns. - Three GUI jobs hung until a six-hour reaper. AI-generate agent, AI-generate hook and AI CLAUDE.md all reached an interactive terminal primitive on a background thread, where no keypress is ever coming: nothing logged, nothing timed out, and the interface said "running" the whole time. Each is now split into a non-interactive function the job calls and a thin terminal wrapper. The underlying cause is more general and is now closed generally: a module that did from .ui import text_input held its own reference, which patching ui never moved. The bridge sweeps for the original function instead of naming modules — naming them is what had already failed twice. - claudectl spent accounts that had nothing left. Every internal claude -p call — AI agents, AI skills, MCP analysis, system prompts, AI CLAUDE.md, memory and lessons extraction, Plan → Execute and its council, scheduled loops — launched without checking the account's rate-limit window. On a full window the call failed and you were told "No output from Claude", while a second configured account sat there with headroom. claudectl now stops and offers the accounts that still have headroom: a picker in the TUI, the existing approval modal in the GUI. Unattended work (the scheduler, the detached scan worker, a scheduled loop) never prompts — it records the reason and skips, rather than quietly spending an account you did not offer. The check reads the usage data the plan-usage poller already fetched, so it costs no network call; an unknown limit is never treated as a full one. claude mcp, claude plugin and claude --version are untouched — they cost no quota. - The two foreground runners destroyed the reason a call failed. run_with_progress and run_with_progress_stdin sent the child's stderr to DEVNULL and returned nothing on a nonzero exit, so a rate limit, an expired login and a genuine crash were all reported identically as "No output from Claude". They now capture stderr and latch it in the same place _run_cancellable already did, and the callers report what claude actually said. - Rate-limit detection no longer fires on the model's own output. Reading the whole of stdout to find the real refusal meant a bare 429 matched a token count and a bare quota matched any answer that discussed rate limiting — locking an account out of headless work for fifteen minutes after a run that had nothing wrong with it. - Archived sessions from other accounts were invisible, and the ones you could see could not be restored to the right place. - /api/sessions was re-parsing every transcript on every GUI restart — an 8-second median on a large project. The disk cache existed but nothing on the cold path could reach it. It also now carries a schema number, so a change to what claudectl stores about a session is visible on old sessions instead of only on new ones. - The window kept painting while it was not on screen. CSS animations, a world's overlay and the CRT caret all kept running behind a hidden canvas, and a light or OLED palette flashed the wrong colour during the swap. Blur is debounced, focus is not, and the dashboard's teardown-and-refetch is debounced with it — Qt fires spurious blur/focus pairs on things that are not a focus change. A tab becoming visible inside the debounce window could also leave the page stuck in its blurred state. - Unresolvable projects cost 98% of the project listing. A path that no longer exists on disk was re-walked on every call, forever, because only successful lookups were cached. claude mcp list is cached for 30 seconds now including its failures — which is the case that costs the most, because a slow-failing MCP server is exactly when it is slowest. - The whole OTEL section of the settings page was inert. It read ST.otel_enabled and friends while /api/state nested them under ST.otel, so every field rendered its default no matter what was saved — and pressing Save then wrote those defaults back over the real configuration, including blanking the headers. The payload is flat now, named exactly as /api/settings takes the values back. - Two "Open in editor" buttons had never worked, sending a parameter the endpoint does not read. /api/job//decide — the route every approval gate resolves through — wrote two HTTP responses for one request. - One noisy warning was crowding the event log out. The dedupe key contained the measurement it was deduplicating, so 609 of 674 events were the same warning differing by one decimal. Only decimals collapse now: an HTTP status or a project path with a digit in it is part of the event's identity, and folding those was dropping real failures. Changed - .claudectl/ is created through one helper, and config.write_atomic takes an optional file mode. - The plugin manifest, the marketplace entry, the docs page and CITATION.cff are all checked against pyproject.toml by one test. [1.8.2] - 2026-09-01 Memory was twelve things and the interface could name three of them. This release makes the whole layer legible — what exists, what writes it, when it reaches a session and what it costs — and fixes the four ways it was quietly spending more of your limit than you asked it to. Fixed - Path-scoped rule files were loaded on every turn, not lazily. They were written with globs: frontmatter — Cursor's key. Claude Code reads paths:, and a rule file without it is loaded unconditionally, so the entire point of splitting memory into one file per module was lost while the UI reported each one as free until Claude opened a matching path. Measured on this repo: 22 238 → 18 363 always-on tokens, 3 875 off every single turn. Existing rule files are rewritten with the correct key on the next sync — free, no Claude call. - A failed memory cycle was reported as progress. Failed and skipped units were summed into one pending_units, and every surface worded it "the next cycle takes them" — so on a rate-limited account six failed extractions an hour read as work safely queued. Failures are now counted as failures and shown with the error that caused them. A nonzero exit also discarded the real error text whenever no job was attached, which is exactly the scheduler and the detached background worker. - Auto-memory no longer speeds up when it has a backlog. A capped cycle used to schedule the next pass 45 seconds later, again and again until the project caught up. Across several opted-in projects that is most of a daily limit inside an hour, and on a failing account it was the same dead calls in a loop. One pass runs when claudectl starts and one every interval you configured: the per-cycle cap decides how much a pass may spend, the interval decides how often that happens, and nothing else schedules work. - Opening a project no longer adds an unscheduled memory cycle. Opting a project into background auto-memory also implied "refresh whenever you open it", so the configured interval bounded nothing — and the GUI never consulted the per-project flag at all, deciding from the global setting alone. Background auto-memory now owns the spend; Build with Claude still refreshes on demand. - Disabling or deleting a hook applies to every account. Installing one already did. Turning one off wrote only the account you happened to be looking at, so a hook installed once and disabled once kept firing from the others — with the UI showing it as off. Selecting an account still acts on that one alone. - Session topics no longer list claudectl talking to itself. claude -p leaves a transcript like any other session, so the CLAUDE.md SESSIONS block was spending always-on tokens describing claudectl extracting a module or distilling lessons. - Opening the recall preview no longer counts as reinforcement. It appended to the hits log, so inspecting memory reshaped which facts survived eviction. - The reinforcement log says how many hits are waiting to be folded in, instead of a fixed phrase derived from an unrelated number. - Lifetime memory spend includes lesson scans, which are Claude calls like any other. - A broken @import in a CLAUDE.md is flagged. Claude loads nothing for it and says nothing about it. Added - The Memory tab inventories every memory artifact — the graph, the CLAUDE.md digest, the AUTOGEN and SESSIONS blocks, the path-scoped rule files, the worklog, both sidecars, the cross-project conventions block, the workspace manifest, lessons, version snapshots and the per-prompt injection — grouped by when each one reaches a session: always on, every prompt, when relevant, or stored and never loaded. Each row says what it does for you, what it costs and what to press. The always-on total is given as a share of the context window, which is the only denominator that makes a token count mean anything. - What a cycle spent and what it dropped. Cost per cycle, cumulative spend for the project, the names eviction removed, the facts recall reinforces most (clickable, showing what each one actually is), and how many sessions each lesson is from being dropped. - The CLAUDE.md tab shows the file block by block — your prose, KEEP-fenced regions, AUTOGEN, SESSIONS and the memory digest — each with its token cost and the one button that regenerates that block alone. - Workspace health is per-check rows, each carrying the points it is worth and the button that clears it, instead of terminal output. - Version history sits beside the artifact it belongs to — graph history on the Memory tab, CLAUDE.md history on the CLAUDE.md tab — with recent versions shown and the rest collapsed. - The recall preview says why each entity was picked. - The next pass is stated wherever a cycle left work queued, so "still queued" has an answer to "until when". Changed - The CLAUDE.md memory digest carries facts, not a table of contents. It was spending its budget listing module names — which tells a session nothing ls would not — and now spends it on the highest-signal lessons and the module dependency edges. - The Audit tab is now only what a turn costs across every surface at once, project-scoped and account-scoped together, with the version history it used to host moved beside the files it belongs to. - Generated files (.claude/rules/claudectl-mem-*.md, docs/api.md, plugin/skills/) are marked linguist-generated, so their diffs collapse in review. [1.8.1] - 2026-08-31 Fixed - Auto-memory crashed in the GUI, with Memory update failed: 'NoneType' object has no attribute 'reconfigure'. The stale-on-edit work added in 1.8.0 imported a hook script for one path helper, and a hook reconfigures stdout when it is imported — correctly, since Claude Code hands it a pipe. The GUI runs as a windowed process with no console, where sys.stdout is None, so that line took down the whole refresh cycle. In the GUI auto-memory therefore never ran at all: the hourly pass failed silently on every project, and opening a project only made the same failure visible. The dependency now points hook → library, never the other way, and a gate walks the package for any module importing an entry point — it found two more instances immediately (see below). - The context-weight audit under-reported two always-on hooks. It imported minimalcode_hook and concise_hook for their rule text inside a bare except Exception: pass, so in the GUI the import failed and both hooks' per-session cost silently vanished from the total. The text now lives in hookrules.py, which the audit and the hooks share. - A hook no longer dies when there is no stdout. Setting UTF-8 on a pipe is right; being the reason the hook exits when stdout is absent is not. - Auto-memory runs on launch and on the interval, from either interface. The periodic pass had one caller, in the GUI's server startup — so on the terminal side "keep this project's memory updated automatically" only ever happened when you opened the project. The scheduler loop itself had no test beyond "the stop flag can be set"; the launch pass, the repeat and the stop are now covered. [1.8.0] - 2026-08-31 Added - claudectl --help (and -h, help, --version, -V). A pip install used to answer the most obvious command by opening a full-screen terminal UI. The help text covers every subcommand, what the tool does and where its state lives; it is answered from cli.py, which imports the standard library and nothing else, so it costs nothing and cannot be broken by anything in the TUI stack. A test enumerates the dispatch table from the source, so the text cannot fall behind it. - Hide projects you never want to see. Every folder Claude Code has ever run in shows up in the project list, including one-off experiments and folders that no longer exist as work. A project can now be hidden from the TUI project menu and the GUI sidebar — from Hide / restore projects in the main menu, or the Hide button on a project page. It is a view flag (project_defaults[].hidden), so nothing on disk moves: the sessions stay resumable and restoring costs one click. The TUI says how many rows are filtered; the GUI grows a Show N hidden projects button while any are. - The Skills section shows what Claude Code actually loads. It listed claudectl's own private library (~/.claude/claudectl-skills) — a directory nothing reads — beside a "Project skills" card that could never fill, because opening a global page ends project context. Now it is the four scopes Claude Code really resolves (personal, project, plugin, bundled), each row carrying the command you type and its real usage count from Claude Code's own skillUsage counters, plus the starters to install from. What was in the private library is migrated into /skills once, and sync-accounts levels skills across accounts like everything else. - Desktop notifications when a background job that ran longer than 20s finishes, and when the detached memory worker is done — which had no interface of any kind before. One hook in the job runner, one in the worker; Settings → Notifications turns them off. - Loops, including ones that run with nothing open. A /loop is session-scoped — it fires only while its session is open and idle — so claudectl offers both: start one in a session (and watch it through that session's transcript), or in the background, where claudectl registers an entry in Task Scheduler (cron elsewhere) that runs headless claude -p on the interval, under the account you pick, with claudectl closed. A background loop carries its guardrails in the runner rather than the UI: a permission mode you choose (claude -p starts in Manual and would otherwise do nothing), a 7-day expiry that the scheduled run enforces on itself, your per-call budget cap on every run, a notification when one fails, and a log of the last twenty runs with their cost. Each run is a fresh session that reads a rolling CLAUDECTL:LOOP record in the project's CLAUDE.md — rewritten every time, capped at five entries, so it cannot grow. loop.md is edited on the same page for either scope, with an AI draft behind the usual approval gate. - Agents that actually get delegated to. Copying agent files into a project makes them available; Claude Code still picks a subagent by matching the task against its description, and nothing reads the body — measured here, agentLastUsed held one entry against ten installed agents. Four levers now: a CLAUDECTL:AGENTS delegation table in the project's CLAUDE.md (the one file read on every turn), Sharpen descriptions (one Claude call rewrites each installed agent's description into Use PROACTIVELY when … form, diff-approved, bodies untouched), an optional suggest-subagent prompt hook that names a keyword match with no model call and stays silent otherwise, and a last-used marker in the picker so the dead weight is visible. - Output styles explain themselves, in scope order with the active one and the file that pins it named, and four claudectl starters (Terse, Reviewer, Pair, Ship) to copy. - Global CLAUDE.md has its own page. It was the third card at the bottom of the MCP servers page. - Search boxes on the agent library, the project agent picker and the skills inventory. - Skills usage that means something. skillUsage counts one thing — the times you typed /name — so a plugin that works through a SessionStart hook reads as unused forever (caveman: "used twice, 56 days ago", while shaping every session). The page now shows that counter merged across every account beside a second, measured signal from your transcripts: in 27 of your last 30 sessions. Two flags cover the rest — manual only (disable-model-invocation) and thin description, the two reasons a skill silently never fires. Personal skills install into, and delete from, every account. - Auto-memory that actually runs, and converges. Turned on per project (memory hub o, or the checkbox on the GUI memory tab — one flag now honoured by both interfaces and the detached worker), a project's memory no longer goes stale while it is on. Each cycle extracts what its budget allows and leaves the rest queued; the scheduler returns in seconds rather than after the full interval until the project has caught up. It bootstraps a project with no graph at all, so the first build no longer has to be manual, and it runs from the TUI as well as the GUI. - A stale-on-edit hook. memory-stale-on-change records the files Claude edits, so auto-memory re-extracts exactly those instead of walking the project. The periodic scan remains the reconciler for edits made outside Claude Code. - Nothing shrinks without a way back. Pin any entity or lesson and the importance cap can never evict it. Fence a section of CLAUDE.md between CLAUDECTL:KEEP markers and AI compression never even sends it to the model. Prune names the exact session entries it will drop and asks first — the GUI destroyed silently while the TUI confirmed. Twelve versions of CLAUDE.md and of the memory graph are kept, browsable with a diff and restorable from the context-audit page; restoring is itself snapshotted. - "What to work on" is worth reading. Four new free signals (stale context with the key that clears it, TODO/FIXME markers, deferred ponytail: shortcuts, untested modules) plus an optional one-call Find work scan that adds bugs, vulnerabilities, slow paths and functions worth building. Findings persist, so the card stays instant. - Sharpen descriptions is machine-wide. It lived on one project's tab and rewrote that project's agents only. It now covers every account's agents, every project's, and the library — grouped so the same agent in twelve projects is one question and twelve writes, behind one approval gate. - Controls for the memory settings that had none. Seven of fourteen lived only in claudectl.json, including memory_max_calls, which the memory hub and the health check both told you to raise. - Per-cycle cost, and what a cycle actually did, in the memory hub and the GUI. The real figure was captured from every headless call and read by nothing. Fixed - Auto-memory did nothing at all once more than six modules had changed — the harder you worked, the less it updated — and it could never build a project that had no graph yet, so "keep this updated automatically" required a manual build first. The GUI checkbox wrote a flag only the GUI scheduler read, while the TUI and the worker gated on a setting with no control on either surface, so ticking the box did nothing outside a running GUI window. - A failed extraction wiped a module's memory and never retried it. A Claude call that timed out returned an empty result indistinguishable from "this module has nothing", so every fact already known about it was marked superseded — and its file hashes were recorded as current, making the loss invisible to every later check. A capped run did the same to every module it skipped. Provenance now advances only for modules actually extracted, and save_memory failing is no longer ignored at five call sites. - The staleness check read every source file in the project, in full, to hash it — on every scheduler tick and every project open. Files are compared by (mtime, size) first and re-hashed only when that moves; content stays the source of truth, so touching a file still costs no Claude call. - A memory refresh reported success after crashing. The GUI badge read the scan lock disappearing as completion, which a failed cycle does exactly like a successful one; the detached worker announced "Memory updated" when nothing had run, and told nobody at all when it failed. - The query the returned 33 entities. The recall IDF could never reach zero and a positive score was the only gate, so with the prompt hook on, memory was injected into prompts that asked for none. Ranking now fuses four signals by position (BM25, path, dependency rank, and a confidence-weighted lesson signal) instead of adding four quantities on four different scales, and a contentless prompt retrieves nothing. - Recall reinforcement had never worked. The counter that decides what eviction keeps was folded in only by a refresh that had work to do — so on this repo 807 recorded hits had produced a maximum counter value of 1 — and it credited every entity that ranked rather than the ones that fit the budget and were actually injected. - One generated rule file was always loaded and another could never load. The path-scoped glob was built with a character prefix, so {tests/, tools/} became t/** (matching nothing, ~375 tokens that never loaded) while a unit whose files diverged at the first segment became ** — permanently in context, in the one feature whose whole purpose is laziness. - The memory-stale-on-change preset had never marked anything stale. It was bound to an event whose matcher is a list of literal filenames, and it invoked a script that does not touch memory. - A workspace could not stop reporting itself stale. Only two operations recorded a freshness baseline, so rebuilding memory — the very thing the screen tells you to do — could never clear it. Checks that contribute nothing to the score no longer show a warning dot, and every missing point now prints what recovers it. - claudectl could rename Claude Code's live .claude.json out from under it. Reading a file another program is writing occasionally catches a half-written one, and the corrupt-file quarantine treated that as damage worth preserving. - Cross-project conventions was permanently empty and described inputs it does not read: it scanned one account, one directory deep. It now reads every account, accepts decision lessons, and lists near-misses with a Pin button instead of a dead end. - A finished job no longer repaints the page you moved to. onDone handlers called drawMemory() / drawPage(…) unconditionally, so building memory and walking away put you back on the memory tab minutes later, over whatever you had opened. The refresh is now a guarded redraw in the job runner, gated on the view the job started from. - The view button on a built-in output style showed "(empty)" — those ship inside Claude Code and have no file, which read as a broken button. - Selecting an output style with no project open threw, because every write assumed CUR.path. The same bug made the Skills and Output styles pages' project sections structurally unreachable: navigating to a global page clears the current project, so the "this project" card could never have shown anything. They use the last opened project and say which one it is. - A folded YAML description (description: >-) parsed as the literal >-, so every plugin skill looked as if it had no description at all. - load_settings() copied the defaults shallowly, so every load of a settings file that named no project_defaults handed back the same dict — and each writer of one mutates it in place. In a long-lived process (the GUI, a long TUI run) one project's pins leaked into the next load. [1.7.0] - 2026-08-28 Added - What you provision now reaches every account, not just the default one. Hooks, plugins, marketplaces, user agents and the global CLAUDE.md were all written into whichever config directory happened to be active when the module was imported — in practice the default account. Measured across five configured logins, the default had 18 hooks, 3 marketplaces, 3 plugins and a global CLAUDE.md; the other four had none of it. The status line was the only feature that ever reached all of them, because it was the only one with a real fan-out; that fan-out is now the rule rather than the exception. - claudectl sync-accounts levels every account up to the union of them all, on the command line, on the GUI's Accounts page and in the TUI accounts menu. It shows the per-account diff before writing anything, only ever adds (an account keeps whatever the others lack, because nothing can tell a deliberate choice from a gap), routes every plugin install through the same review gate a single-account install uses, and reports nothing to do when re-run. - The Plugins page says which accounts have each plugin and marketplace, and offers to install one into the accounts that do not. Adding a marketplace registers it everywhere; removing acts only on the account on screen. - Nineteen functions that existed only in the terminal UI now have a surface in the GUI: the editor / claude.exe / config-dir paths and the headless budget cap; disabled hooks, an enable/disable control and an Edit settings.json button; the per-prompt recall hook, path-scoped rules and the recall budget, which the GUI had been printing read-only; the global CLAUDE.md viewer and editor; claude mcp get detail and MCP tool docs, which the analyze job used to write into a file the GUI could not open; env vars and headers when adding an MCP server; the one-key project setup the terminal offers as !; the architecture stats card, which revives an endpoint that had no consumer at all; tools and model when creating an agent; copying a skill into your library; and a Help page generated from the navigation and the terminal's own key table instead of retyped. - Cross-project conventions, loop.md at both project and account scope, Claude Code's own per-project record and output-style previews — four endpoints that had no control in either surface — are reachable. Fixed - claude plugin … and claude mcp … ran with no environment, so they acted on whatever account the process inherited while every reader resolved the configured one: with claudectl switched to another account, the Plugins page listed that account's plugins and Install wrote into the default. Both now name an account explicitly. - mcp.update_global_claude_md_mcp and the conventions sync wrote a file Claude Code reads every session with a plain open(..., 'w'). A write that dies partway broke the whole session, not just claudectl. Both are atomic now. - A hook disabled in the terminal vanished from the GUI, which then offered its template as uninstalled and created an enabled duplicate beside the disabled one. - The job list on the dashboard raised as soon as any job had said anything: every producer appends a {ok, text} record and the reader indexed it as a string. - The per-project recall toggle promised a hook that only one account had installed. Changed - The GUI's accepted-settings list is derived from the settings registry minus an explicit internal set, rather than being a sixth hand-maintained copy that had already fallen behind it; the terminal's settings rows are named after the settings they write, so the two screens can be compared by a test instead of by hand. - New parity gates, each watched failing under mutation: every route the SPA never calls must carry a written reason, a POST route must be reached with post(, a value rendered as on/off must be one the page can send back, the main menu's rows must name a GUI counterpart, and the account fan-out helper must have callers. The browser smoke tool walks the page and tab lists derived from the app instead of a hardcoded subset, and its floor rose from 40 checks to 85. Fixed - The launch picker's effort slider pointed at the wrong label. EFFORTS had grown a seventh entry (ultracode) but the tick row was six labels typed into the markup, so the thumb at xhigh — 4/6 of the track — sat under HIGH, and ultracode was a stop with no label at all. The row is generated from the same list that sizes the slider, and each label is placed on its own stop rather than spread with space-between, which aligns label boxes and drifts every centre off its tick. tools/shot_gui.py now measures thumb-against-label for every stop. Added - The sessions screen's 29 keys are described in one place instead of three. The action table, the / palette and the help screen had drifted apart — the palette was missing code review (R) and the help screen was missing R, plan→execute (X) and context injection (K), so code review had no discoverable entry point anywhere in the product. Both discovery surfaces are now generated from the table, and a test walks the key handlers in the source and fails when one of them has no row. - The launch picker reads the model list from Anthropic instead of a hand-edited table, so a model released this week is selectable without a claudectl release. It uses the login Claude Code already holds — no API key — and refreshes once a day on a background thread. Facts (context window, supported efforts, release date) come from the API; cost rank, capability rank and the "best for" prose stay curated and are inherited by family, so a new generation appears with sensible columns rather than blanks. Every failure — no network, logged out, auto_update: off — falls back to the list shipped with this version, never to an empty picker, and a model you have pinned that Anthropic has retired keeps its place in the picker with a warning instead of being silently reset to default. - claudectl reports its own version against PyPI and can update itself. A banner says when a newer release exists, the Updates screen and the Plugins page offer the upgrade, and Settings → Updates chooses between being told, installing on quit, and never checking. The upgrade runs in its own window after claudectl exits, because pip cannot rewrite the console script of the process running from it — and a git checkout is told to git pull rather than having a release installed over the top of it. - Claude Code plugin distribution — claudectl installs as a plugin, with its bundled skills generated from the packaged templates and validated in CI. - Multi-repo worktrees board and repo discovery, including submodule and linked-worktree classification read from the gitdir: pointer rather than a subprocess. - Model failover proxy with TUI and GUI settings, so a dead model is retried instead of hanging the session. - Desktop GUI overhaul: dashboard home, always-on account usage, a single 3D background stage, chassis skins and worlds, and an instrument dashboard. - OmniRoute-aware dashboard and usage views, including free-tier session tagging. - Resumable saved plans in Plan → Execute. - Status line, installable per account. - macOS and Linux support alongside Windows. Changed - TUI/GUI parity is enforced as a CI gate rather than reviewed by hand; the API reference in docs/api.md is generated from the live route tables and checked for staleness. - Per-turn work moved off the hot path — the status line dispatches before the TUI is imported, and hooks no longer re-stream whole transcripts on every turn. - Duplicated primitives consolidated into single implementations for transcript reading, process control, JSON storage and session paths. - Default models updated to Opus 5. - README rewritten around real screenshots. Fixed - Status line resolves the active account and project rather than the checkout it was installed from, and reports why an installed status line is not showing. - Account-scoped settings writes no longer bind the config directory at import time, so every account is written correctly. - Both local HTTP servers authenticate requests, with a host allowlist, browser fetch-metadata rejection and a per-run secret. - State writes are atomic, and a file that exists but will not parse is quarantined instead of silently erased. - Malformed requests return 400 instead of 500, and cfgdir is validated against known accounts. - Session paths are resolved from the transcript's recorded cwd rather than decoded from the encoded folder name, which had dropped UNC-path projects entirely. - Packaging: the skills templates glob now matches, so the wheel ships them; a CI job installs the built wheel and reads the data files back out of site-packages.