Skip to main content

charter

charter is a control plane for Claude Code agents working across many repos on GitHub or GitLab. It gives an agent durable personas (specialist role identities, each with its own committed memory and a scoped credential vault), isolated per-task workspaces for cloning and working several repos in parallel without mixing them up, and a vault that keeps credentials out of the model's context — so an agent (or a whole team of them) can move between repos and tasks without losing what it has learned or leaking a secret into a transcript.

If you've never seen it before, you can go from uv tool install charter-cp to a working control plane in about a minute — see 60 seconds below.

Install

charter ships as two artifacts — install both. A CLI-only install leaves the plugin's hooks inert (no session context injection, no golden-rule guard, no auto-save), since the plugin is what actually wires them into Claude Code.

1. The CLI

uv tool install charter-cp     # installs the `charter` command

Lead with uv for a concrete reason, not a preference: charter requires Python ≥ 3.11 (it leans on stdlib tomllib, which is 3.11+ only), and stock macOS ships 3.9. uv tool install can fetch and manage a suitable Python for you; pipx and pip both require one to already be on your PATH.

Alternatives, once you have a 3.11+ Python:

pipx install charter-cp        # installs the `charter` command
pip install charter-cp

Or run it without installing anything:

uvx --from charter-cp charter <cmd>

The package is charter-cp; the command is charter. PyPI would not allow charter as a project name, so the distribution carries a suffix — but everything you type, and everything in this README, is charter.

2. The Claude Code plugin

This repo also ships as a Claude Code plugin — .claude-plugin/plugin.json + hooks/hooks.json — installed the way you install any Claude Code plugin from a git repo (inside a session: /plugin marketplace add diazoxide/charter, then /plugin install charter@charter; consult Claude Code's own /plugin help if that flow has moved on since this was written).

The plugin supplies the pieces that only make sense running inside a Claude Code session: injecting the active persona's memory at session start, the PreToolUse guard that enforces the one-credential rule below, the record-memory nudges, and the Stop-hook auto-save. The plugin ships no Python of its own — every hook it declares just shells out to the charter CLI you installed in step 1, so the CLI must be on PATH first. The CLI works standalone for everything else (charter clone, charter persona show, …) with the plugin absent; install the plugin too if you want charter actively driving a live session, not just scripted from a terminal.

60 seconds: from nothing to a working control plane

mkdir my-control-plane && cd my-control-plane
charter init --forge github --owner my-org
charter doctor
charter discover
charter clone some-repo
  • charter init scaffolds charter.toml, the baseline directories (personas/, inventory/, workspaces/), a .gitignore tuned for the layout, and a Claude Code status line — additive and idempotent, so re-running it is always safe. --forge is gitlab (the default) or github; --owner is the GitLab group or GitHub org/user whose repos this control plane tracks.
  • charter doctor preflights the environment (python, git, git identity, the forge's CLI and its auth) and tells you exactly what's missing before anything else tries to use it.
  • charter discover queries the forge and writes inventory/repos.json — the durable, git-tracked map of every repo in the group, complete even when nothing is cloned yet.
  • charter clone <repo> clones a repo on demand into the active workspace (workspaces/default/<repo>/), already configured with the one-credential git policy below.

Concepts

  • Control plane — any directory marked by charter.toml. Not a fixed location: cd anywhere beneath one and commands resolve it by walking up, the way git resolves .git. See docs/control-plane.md for the file in full.
  • Workspace — an isolated, per-task directory of repo clones (workspaces/<name>/<repo>), so several tasks can each hold their own repos on their own branches without stepping on each other. default always exists; charter workspace create <name> --use starts a new one.
  • Worktree — a further split within one workspace's clone of a repo: several git worktrees over one clone (workspaces/<ws>/.worktrees/<repo>/<piece>), so parallel sub-agents can each work their own branch of the same repo without re-cloning it.
  • Persona — a specialist role identity (devops, qa, …) with a committed charter, its own persistent memory, and a named credential vault — dispatchable as an isolated Claude Code sub-agent. This is charter's differentiator; see the worked example below and docs/personas.md.
  • Memory — durable notes a persona or workspace records as it works (charter persona remember / charter workspace remember). How far a note travels — disk only, committed locally, or pushed to the team — is one setting, [memory].share, and it defaults to local: see docs/control-plane.md.
  • Vault — where a persona's credentials live: plaintext JSON at file mode 0600, with no encryption at rest. What it protects against is different and real — keeping a secret value out of an agent's context and transcript. Read docs/secrets.md before storing anything real in one; the vault is not a password manager.

Worked example: a persona, end to end

charter persona create devops --role "DevOps Engineer" \
  --delegate-when "CI/CD pipelines, k8s deploys, cluster access" --with-vault
charter persona use devops
charter persona secret set API_TOKEN --stdin           # value never touches argv/history
charter persona remember "prod kubeconfig lives in the devops vault, key KUBECONFIG"
# …write what the persona owns, then drop the `draft: true` line it was created with:
charter persona sync-agents

A new persona starts as draft: true and gets no generated sub-agent until that line is removed — an unwritten charter must never become an agent's system prompt. charter persona lint, charter doctor and the status-line chip () all say so meanwhile.

The last step writes .claude/agents/devops.md — a generated Claude Code sub-agent carrying devops's charter, its memory instructions, and a reminder to use the vault (exec/cp, never --reveal). From here on, any session can hand work to it in an isolated context instead of guessing with borrowed credentials:

Agent(subagent_type: "devops", prompt: "Check whether the prod deployment rolled out cleanly.")

The devops sub-agent runs with its own vault and its own memory — it can read the prod kubeconfig note it (or a teammate) recorded earlier, pull API_TOKEN via charter persona secret exec, and never expose the raw value back to the caller. Every dispatch like this is tallied (agent name + date, never the prompt) so charter persona stats can show whether devops is actually being used, or whether that work is quietly routing to a generic agent instead. Full format, inheritance, and the memory model: docs/personas.md.

Feeding a tool that wants a dotenv secrets file

Some tools take a file of secrets rather than env vars. --dotenv writes one 0600 temp file containing every entry you name, points an env var at its path, and deletes it when the command exits — so no value is ever printed, stored, or placed in argv.

charter secret exec qa \
  --dotenv PLAYWRIGHT_MCP_SECRETS_FILE=APP_USER:platform-user \
  --dotenv PLAYWRIGHT_MCP_SECRETS_FILE=APP_PASS:platform-pass \
  -- npx @playwright/cli@0.1.18 -s=login fill e3 APP_PASS

Repeats sharing an env-var name merge into a single file, in flag order. Different names produce separate files. Defining the same NAME twice under one ENVVAR is an error (exit code 2).

The value is never typed by the caller: the tool refers to the secret by the name you gave it (APP_PASS), and resolves it from the file. Any value that does appear in captured output is redacted.

--dotenv cannot be combined with --exec — exec replaces this process, so the temp file would never be cleaned up. Use --env for an exec'd command.

The one-credential rule

Every git operation charter performs — from any persona, any sub-agent, any repo clone — authenticates with that repo's own forge's CLI token, over HTTPS: glab for GitLab, gh for GitHub. Never an SSH key, never commit/tag signing. charter git-policy --apply writes this into a repo's local git config (a credential helper, commit.gpgsign = false, and SSH→HTTPS URL rewrites so even a repo whose remote is an SSH URL still transports over HTTPS); charter clone applies it automatically to everything it clones.

This is deliberate, not incidental: an SSH key prompt or a GPG signer prompt hangs an autonomous agent mid-run — there's no human at the keyboard to answer it. One credential, held by the forge's own CLI, over HTTPS, is the only shape that can never block on a question nobody is there to answer.

The Claude Code plugin's PreToolUse guard denies a command that would bypass this — a raw SSH GitLab/GitHub URL handed to git, GIT_SSH_COMMAND=, -S/--gpg-sign, ssh -T git@github.com. If you hit one of these denials, that is the rule working, not a bug — the message names the fix (usually: nothing, since charter git-policy --apply already configured the repo correctly). Check the credential with glab auth status / gh auth status, never ssh -T.

Learn more

  • docs/control-plane.mdcharter.toml in full: every key, a self-hosted example, a mixed-forge example, and the memory posture (local/commit/push) in detail.
  • docs/forges.md — what GitLab and GitHub each need, self-hosted hosts, and the rule for a repo name that collides across forges.
  • docs/personas.md — the charter format, the memory model, and dispatching a persona as a sub-agent.
  • docs/secrets.md — exactly what the vault does and does not protect against.

Development: the test suite is stdlib unittestpython3 -m unittest discover -s tests. Report issues at github.com/diazoxide/charter.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

charter_cp-0.11.1.tar.gz (323.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

charter_cp-0.11.1-py3-none-any.whl (203.4 kB view details)

Uploaded Python 3

File details

Details for the file charter_cp-0.11.1.tar.gz.

File metadata

  • Download URL: charter_cp-0.11.1.tar.gz
  • Upload date:
  • Size: 323.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for charter_cp-0.11.1.tar.gz
Algorithm Hash digest
SHA256 3163f91884876a97277630efd1126a58413ae03845c93834c67330fa7e926c7e
MD5 40efacd443530695b993856960c01b4e
BLAKE2b-256 97c5d98e3511d6230c7e69e26f812291c0831ac116f4aa8c77c177dd76c302bd

See more details on using hashes here.

Provenance

The following attestation bundles were made for charter_cp-0.11.1.tar.gz:

Publisher: release.yml on diazoxide/charter

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file charter_cp-0.11.1-py3-none-any.whl.

File metadata

  • Download URL: charter_cp-0.11.1-py3-none-any.whl
  • Upload date:
  • Size: 203.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for charter_cp-0.11.1-py3-none-any.whl
Algorithm Hash digest
SHA256 3dc1d56c128acce442ccbf1c3dfc596557d1fafbca9741f2f649b98e21616667
MD5 e10cbf9272fbe3c3eea622e49ec4337d
BLAKE2b-256 25e0dee80640c9ba63214a26ad99c953a3b5541456aba6da9f081690ee32f561

See more details on using hashes here.

Provenance

The following attestation bundles were made for charter_cp-0.11.1-py3-none-any.whl:

Publisher: release.yml on diazoxide/charter

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page