Every project on this site was built with Claude Code and Codex working side by side. This repo is that setup, exported and scrubbed: the hooks, the command-line tools, the model routes, the memory pipeline and the server recipes, plus an installer that puts only the parts you pick on your own machines.
I run my agents without permission prompts, because stopping to click "allow" a few hundred times a day is not a workflow. That only works if the safety lives somewhere other than the prompt. Most of this kit is the answer to that one question: what has to be true, in code, so an agent can run unattended and still never print a secret, push private data to a public repo, or quietly burn through a week of usage on the wrong model?
# what this machine already has, then the plan, then the install $ install/doctor.sh --profile odin $ install/install.sh --profile odin,telegram --dry-run $ install/install.sh --profile odin,telegram # which model and effort each kind of job gets $ KIT_HOME=$PWD framework/harness/bin/route list class lane model effort feature-build claude.subagent claude-opus-5-5 high long-unattended-build codex.build gpt-6.1-sol high security-review codex.plan gpt-6-astra xhigh bulk-sweep claude.sweep claude-haiku-5-5 medium private-data local.text qwen3.8-text:27b n/a
My first setups were piles of instructions: a long rules file, a growing list of skills, notes telling the model which tool to use for what. They drifted. When I audited the whole workflow in October 2026, the numbers were plain. One web-tools rule was written in 8 places and still missed 6 times. The memory index had been overwritten and 142 of 185 memories went dark for 22 days. About 75k tokens loaded before the first prompt of every session, and every subagent paid about 54k of that again.
The same audit showed where things worked. The day that web rule became a real command-line tool registered as a skill, it got about 1,900 calls in 12 days and the banned tools dropped to zero. So the kit follows one idea: when a rule matters, ship it as a hook, a CLI, a lint or a test. Keep the instruction files short, and put the rest in code that runs whether or not the model remembers.
The kit is organized as profiles, one per job a machine can have. Each profile is a profile.json that lists tool ids from one manifest, install/tools.json, plus the services, timers and rules that machine runs. The names come from my own home lab, and they are just role names here.
The laptop where Claude Code and Codex run: the hook dispatcher and secret firewall, the public-push gate, the model routes (route, cx), the web CLI, the vault with prompt-time search, automatic session memory, a weekly review, a harness lint and a CPU ceiling for agent fan-outs.
An old laptop as a server: uptime checks that know when a box is asleep on purpose, Wake-on-LAN, a jobs registry with kill switches, the vault-search embed server, a backup target and a deploy kit (Compose, Caddy or Tailscale Serve, a systemd unit, a health check, a restore script).
A GPU box for Ollama and Whisper that sleeps when idle and wakes on demand, a media server behind a one-way firewall, a thin client that only connects, and a Telegram add-on where every alert is silent except heat warnings.
Nothing installs until you confirm. Every file the installer replaces gets a timestamped backup beside it, and your agent config is only merged when you pass --harness. Third-party tools are downloaded from their official source at install time, never copied into the repo.
Every tool call goes through one dispatcher. Claude Code's PreToolUse hook starts a single Python process that runs each before-hook whose matcher applies, then merges the answers: deny beats allow, and every reason is kept. Moving five hooks into one interpreter halved the hook cost of each Bash call, from about 0.3 to 0.15 CPU-seconds. If a safety hook crashes, times out or prints something unreadable, the call is denied. An ordinary hook that fails is just dropped.
The secret firewall reads operands, not raw text. It treats a file as read only when it is the argument of a read verb, the source of a copy, a redirect, or a path opened inside python -c, a heredoc or an ssh command. That means rg input_tokens passes while quoting tricks, hex escapes and variable indirection around a credential file get caught. It is scored against a 182-case replay corpus (96 must-block, 86 must-allow), and the docs list what it cannot catch right next to what it can. It is a guard against accidents, not a security boundary, and the docs say so.
One table decides models. lanes.json maps each kind of job to a lane, a model and an effort level. route prints it, cx dispatches Codex from it, and a route-guard hook denies any subagent call that names no model, so nothing silently falls back to the most expensive default.
Memory writes itself. At the end of every session, a distill step writes a short handoff into a plain-markdown vault. On every prompt, a hook searches that vault and injects the few most relevant notes. The embedding server lives on the always-on box, so search does not fall back to keyword-only when the GPU box is asleep.
The repo tracks what actually runs. Everything under framework/ is exported from my live setup by tools/sync-kit.py: an allowlist of files, a scrub pass, a syntax check and a secret scan. A pre-commit hook in the repo scans every staged file for secrets and private terms and refuses author emails that are not GitHub noreply addresses.
The public-push gate is the piece I rebuilt the most. Its job sounds simple: before anything reaches a public GitHub repo, scan it for secrets and for my own personal data. The hard part was every way a scan can quietly cover less than you think.
A file added in one commit and deleted in the next is gone from the tree but still in the history. A secret inside a zip or a PNG never shows up in a text grep. A file named like a test fixture gets skipped by path rules. A scanner that hits an error can exit 0. And an override meant to skip one slow step can end up skipping all of them.
So the gate scans every blob version in the pushed history, under neutral file names so no path rule can skip it, with archives unpacked and printable strings pulled from binaries. It passes its own scanner config with --config, so a .gitleaks.toml committed to the scanned repo cannot weaken it. A scanner error, a partial scan, an unreadable blob or a missing denylist blocks the push. The one-time override skips only the Codex review; the secret scan and the denylist always run.
The second hard thing was keeping the model choices honest. At one point the main session's model flipped 15 times in 41 days, because an interactive model switch rewrote the global default and every subagent inherited it. The gate itself once pinned a retired model, so every public push failed. That is where lanes.json, apply_lanes.py and the route guard came from: one table, one script that writes it into every tool's config, and a lint that checks the live configs against the table.
The third was tests. A test that imported the memory pipeline before its fixture set the temporary paths wrote test notes into my real vault, twice in one day. The fix went into the code, not the test: the pipeline now refuses to import under a test runner until its state and vault paths are set.
A GitHub Actions workflow runs the manifest, profile, docs and lanes checks, the offline self-tests of the bundled tools, the installer test, a macOS setup test in containers and gitleaks on every push and pull request. These are the numbers from a fresh clone on Oct 18, 2026, run against a throwaway home directory:
The four skips are tools that need a live systemd user session or the optional sqlite-vec extension. The firewall the corpus replaced caught 13 of 52 cases; the current one has 0 misses and 0 false blocks, and on 10,000 real commands it denies 0.23 percent of them.
The less countable result: when a rule now matters to me, my first question is which hook, CLI or lint should hold it, not where to write it down.
The code is mostly bash and Python 3 using the standard library first, with a little JavaScript. There is no framework and no build step. Each bundled tool carries its own offline self-test, and tools/run-selftests.sh runs all of them with an empty environment.
The machines, AI tools and services this workflow runs on.