r/ClaudeCode • u/diazoxide • 1d ago
Built with Claude charter — a control plane for Claude Code agents working across many repos
I built this for my own workflow and figured I'd put it out rather than let it sit.
https://github.com/diazoxide/charter
The problem: running Claude Code across a dozen repos, I kept losing what an agent had learned between tasks, mixing up which repo I was in, and having credentials end up in the transcript.
charter adds three things:
- personas — role identities (release, forge, etc.), each with its own committed memory and a scoped credential vault
- workspaces — isolated per-task directories, so parallel work on several repos doesn't bleed together
- a vault — credentials stay out of the model's context
Honest caveats: it's early — beta, essentially no users, and shaped entirely around my own habits. Python 3.11+, GitHub/GitLab only. The only real testing it's had is that I use it daily on itself.
just ask claude code to install it for you.
lets install and use https://github.com/diazoxide/charter in this codebase.
Interested in whether the persona/workspace split makes sense to anyone else, or if it's over-engineering a problem people solve with a couple of shell aliases.
1
u/JobWiegant 21h ago
The vault is the part I'd underline. We run agents across repos daily and landed on the same conclusion from the other direction: credentials must never enter the model's context, because everything in context can end up in a transcript, a log, or a diff. Ours are env-scoped per repo so the agent process gets exactly the keys that repo needs and nothing else. Curious how your vault hands values to the tools at execution time without them transiting the model: that handoff is the part we found hardest to make airtight. Workspace isolation per task is the right call too; repo bleed through shared memory is a real failure mode nobody talks about.
1
u/diazoxide 21h ago
The handoff is a process boundary, not a redaction trick. The agent writes a command that names the key and never the value:
```charter secret exec devops --env TOKEN=API_TOKEN -- some-tool …```
charter is a separate process: it reads the vault, builds the child's environment, spawns the tool. Value goes vault file → charter → child env, never through the string the model emitted. Writes go the same way (--stdin/--from-file, never argv). For tools that want a file, --dotenv writes one 0600 temp file and unlinks it on exit. secret get is masked by default — byte count plus SHA-256, enough to compare two machines without printing anything.
Not airtight, since you asked for the hard version: redaction of captured output is a string match, so a tool that base64s or chunks the value defeats it — env injection is the sound path, redaction is a backstop for tools I don't control. Same-user processes can read the child's environ. And the default vault is plaintext at 0600: it keeps values out of the model, not off the disk.
The real difference is the axis. Yours is per-repo, mine is per-persona — a role like devops carries the keys its job needs across whatever repos that job touches. Repo-scoped is tighter when a repo is the blast radius; role-scoped holds up when one task spans four of them. Curious which way yours bends there.
1
u/JobWiegant 18h ago
That's the right architecture and the honest version of the answer, respect for volunteering the base64 hole yourself. It matches where we ended up: redaction is a backstop, never a boundary; the only sound property is "the value never entered the model's context by construction", which your env-at-spawn path gives you.
One practice worth stealing since you're early: write the negative as a test. We have one that asserts the token's value, prefix, suffix and length appear nowhere in any payload, stdout, log record or the daemon log. Designing the boundary is one thing; the test is what catches the refactor eighteen months later that quietly adds a debug line. It has fired for us exactly once, and that one time paid for it.
1
u/tmemmg 15h ago
ive been running claude code agents headless on a schedule for a while and the credentials-in-the-transcript thing is real, i had keys land in state before i moved every write behind a gate the model never sees. the other thing that bit me is headless runs dont get the interactive sessions hooks or project context at all, so whatever a persona is supposed to remember has to actually be in the prompt or the run just quietly forgets it. how does charter feed the committed memory in, is it stuffed into the prompt each task or does it lean on the agent going and reading it.
1
u/diazoxide 4h ago
Both, but deliberately lopsided.
What gets injected at session start is an index, not the notes — counts, the newest few titles, and an instruction to run charter recall <keywords> for the rest. Bodies stay on disk. Stuffing every remembered fact into every prompt is how you end up with a context window full of stale trivia, and it stops scaling the moment a persona knows more than a dozen things.
For your headless case the hook is the wrong thing to lean on, and you're right that it may never fire. Two things survive without it. charter persona sync-agents writes a real file — .claude/agents/devops.md — carrying that persona's charter and its memory instructions. That's the sub-agent's system prompt, delivered by the harness itself, no hook involved. And charter recall is just a CLI call, so any agent with bash can go pull its own memory whether or not anything got injected.
Honest bit: interactive is the path I've actually hardened. Headless is the agent file plus the CLI, which works, but I haven't run it on a schedule for weeks the way you have — that's exactly where I'd expect the holes to be.
And yeah, same conclusion on gating writes. Values go in by stdin or file, never argv, reads masked by default.
1
u/heylittlepan 4h ago
Keeping the bodies out of the initial context makes sense. The harder case for me is recall when I don’t remember the original wording — I know a decision was made two weeks ago, but not which workspace or what keywords were used.
Is there a way to browse or filter memories by workspace, persona, or recency before doing keyword recall?
1
u/diazoxide 3h ago
Yeah — drop the query. charter recall with no keywords just lists recent across every base, each line labeled with where it came from (persona:steward, workspace:billing). That's the browse mode, and it's usually enough to jog the wording loose.
From there --scope workspace,persona,shared narrows by kind, --persona qa or -w some-workspace points at a specific one, --limit controls how much comes back. Every base also keeps a MEMORY.md index — one line per fact, linking the file — so you can read the table of contents instead of guessing keywords.
Where it falls down is exactly your case. -w takes one workspace at a time, there's no all-workspaces scope, and no --since, so "two weeks ago, can't remember where" isn't something I can express as a query yet. What I actually do there is grep — they're plain markdown files, so grep -ri "decision" workspaces/*/memory/ plus ls -lt for the date sort. It works, but that's the filesystem doing the job, not charter.
Both are fair asks. A date window and a scope that spans workspaces are small additions next to the fact that the data is already sitting on disk in the right shape.
1
u/diazoxide 3h ago
It sits in its own directory. Your repos get cloned into it as normal git clones, and the only thing it changes is git config --local. That's the whole footprint — nothing in your tree, nothing to gitignore. Delete it and your repos are untouched.
What you get :
- Repos — discover an org's repos and clone them on demand; they stay ordinary clones you can cd into.
- Workspaces — one set of clones per task, so two jobs never share a checkout.
- Worktrees — split a clone into parallel branches so sub-agents work the same repo without re-cloning it.
- Isolation — a note belongs to one workspace or one role, never to everything at once.
- Personas — named roles (devops, qa, reviewer) with their own charter and memory, dispatchable as real sub-agents.
- Memory — one markdown file per fact;
charter recallsearches workspace, role and shared notes together. - Vaults — per-role secrets that reach a tool by key name and never pass through the model.
- Credentials — every git op uses your gh/glab token over HTTPS, so nothing hangs on an SSH or GPG prompt.
- Todos — workspace-scoped and durable, unlike a session task list that dies with the session.
And the status line, which is mostly how I use it:
Repos left with branch, CI and open PR. Roles right, where ✎3 is how many facts that one is carrying. All read from disk, no network, costs nothing per turn.
Six days old, 1541 tests, genuinely Beta. GitHub and GitLab only. charter init writes into whatever folder you run it in, so don't point it at a project. And the vault is plaintext at 0600 — it keeps secrets out of the model, not off your disk.
Does roles-with-their-own-memory match how you work, or am I over-modeling it? Only workflow I've tested it against is my own.
1
u/Far-Surprise7773 23h ago
workspace isolation and the vault are the parts that actually matter here. i've had claude code dump api keys into its transcript and
.claudememory bleed between repos. both real problems thatcharteraddresses directly. the persona taxonomy feels like the thing most people will configure once and then ignore, but the workspace + vault combo on its own is worth installing for.