Get started with Keystone.
This is a hands-on tutorial. By the end you will have installed Keystone, authored a real charter for a repo, written a guide and a sensor, wired a signal, and understood how vendored policies cascade into your project. Work through it top to bottom — each section builds on the last.
What a charter is
A charter is the set of standards you author to constrain your coding
agents — per repository. It lives at .charter/ as plain, versioned
markdown. CHARTER.md is the single entrypoint; every host file (CLAUDE.md,
AGENTS.md, Cursor rules, Continue rules) is a thin pointer to it. Once the charter is in
place, any agent that can read .charter/INDEX.json and the files it points
at can operate the project. Keystone the toolchain authors and maintains the charter, but
is not required at runtime.
Harness vs. charter
Keep two roles straight, because they are easy to conflate:
- Harness — the engine that runs the model: the coding agent (Claude Code, Cursor, Codex, opencode, …), the orchestrator, the runner. If you are the engine, you are building a harness.
- Charter — the standards you author to constrain whatever harness runs, so each repo gets reliable, quality output. If you author the spec, you are writing a charter.
Keystone is not a harness. It is the agent charter framework: charter engineering (a.k.a. constraint engineering) at the repository level.
.charter/. This guide uses 4.0
vocabulary throughout.
Why per-repo discipline
AI coding agents are productive, and they drift. They forget your conventions between sessions; each repo reinvents the same ad-hoc prompt hacks; org-wide discipline can't be shared, versioned, or audited. A charter fixes that at the level where code actually lives — the repository. Every unique repo gets its own authored standards, and org discipline ships in as vendored policies that every repo pulls, pinned and hash-verified. Swap agents, keep the rules.
CLI
The keystone binary authors and maintains the charter — init, index, project, verify, and the primitive generators.
MCP server
Dispatches the same charter to host agents over the model-context-protocol. Convenience, not required at runtime.
Dashboard
A localhost operator view at http://localhost:4773 — coverage, signals, prune, eval runs.
.charter/ — is enough for any agent to operate the project.
Install Keystone
You'll install the binary, scaffold a charter into a repo, and wire your agent to the MCP server. Three commands, minimum friction.
1. Install the binary
Grab it from the Homebrew tap (or download a release binary):
$ brew install tacoda/tap/keystone ● keystone installed
● keystone v4.0.0 installed
$ cd my-project
$ keystone init
? Select your agent › claude-code
▸ installing charter to .charter/
▸ installing claude-code target → CLAUDE.md → @CHARTER.md
▸ wrote: .charter/INDEX.json
▸ wrote: .charter/lockfile.json
✓ charter installed for claude-code.
$ keystone mcp install --agent claude-code
✓ wrote .mcp.json (keystone MCP server wired)
▶ Next: run /keystone-bootstrap in claude-code.
$ claude
> /keystone-bootstrap
✶ Thinking… (3s · esc to interrupt)
2. Scaffold the charter
From inside a repo, run keystone init. It writes .charter/, a
per-host menu, and asks exactly one question — your agent target. Pass
--agent to skip the prompt entirely (zero questions):
$ keystone init # one question: pick your agent $ keystone init --agent claude-code # zero questions
init is minimum-friction on purpose. Anything that needs a decision at
install time is an opt-in flag, never scaffolded silently. It writes the charter root and
emits the descriptor index (keystone index runs as part of init; you can
re-run it any time).
3. Wire your agent to the MCP server
The MCP server lets your host agent pull the same charter Keystone authored. One line writes the host's MCP config:
$ keystone mcp install --agent claude-code # writes .mcp.json # other hosts: --agent cursor → .cursor/mcp.json, etc.
keystone web serve to open the localhost
dashboard at http://localhost:4773. It's an operator view, not a requirement.
Your first charter
Now look at what init gave you. You'll learn what lives under
.charter/, why CHARTER.md is the entrypoint, and the two commands
you'll run constantly: index and project.
What .charter/ contains
The charter root holds your primitives, the descriptor index, and the state that anchors everything. Here's the shape:
keystone.json stays at the repo root — it holds your policy pins and signal
declarations. Everything else lives under .charter/.
The 13 primitive kinds
A charter is built from 13 primitive kinds. Each carries canonical
frontmatter (kind, id, description, plus per-kind
fields) and lives at a canonical path.
hook kind. It was retired at 4.0 — a reaction
is now a sensor (a check), a tool (a side-effect), or an
agent (a review) that self-subscribes to a signal or host phase via
on:. Pick the kind by what the primitive is, not how it fires.
CHARTER.md, the entrypoint
CHARTER.md is the single canonical agent entrypoint — full orientation for
the repo. Every host file is a thin pointer to it: Claude Code imports it natively
(@CHARTER.md); other hosts get an imperative "read CHARTER.md" plus a per-host
capability delta (which subagents / slash commands / skills the host supports). You author
one entrypoint; each host reads it its own way.
The two commands you'll run constantly
Every time you add, move, or delete a primitive, two commands keep the charter coherent:
keystone index— walks.charter/, parses frontmatter, and emits.charter/INDEX.json. Agents read this descriptor index first and open bodies only when activation conditions match.keystone project— regenerates the host projections (.claude/,.cursor/, …) from your canonical sources.
$ keystone index # regenerate .charter/INDEX.json $ keystone project # regenerate host projections
keystone watch runs an fsnotify loop that re-runs
index + project + lint on every save under .charter/. Keep it in a
side terminal while you author.
Your first guide
A guide is an ambient, glob-scoped directive — the workhorse primitive. You'll author an
idiom guide, pair it with a corpus entry, scope it with globs, and watch it project into
.claude/rules/ and other hosts.
Say your repo has a Rails backend and you want migrations to stay small and reversible. You'll capture that as an idiom guide. Scaffold it:
$ keystone new guide idioms/rails/migrations
That writes a guide file and a paired corpus stub. Author the guide body with canonical frontmatter:
# .charter/guides/idioms/rails/migrations.md --- kind: guide id: idioms/rails/migrations description: Migrations are reversible and small. globs: ["db/migrate/**"] severity: should corpus: corpus/idioms/rails/migrations --- Keep each migration reversible and scoped to one change. Prefer `change` over `up`/`down` when the operation is reversible. Never mix a schema change and a data backfill in the same migration.
How globs scope activation
globs: narrows a guide — it can only remove the guide from
activation, never expand it. The rule is:
activates ⇔ (topic default fires) ∧ (globs match, or no globs declared)
With globs: ["db/migrate/**"], the guide activates only when the ambient
topic already applies and a touched file is under db/migrate/.
Globs reflect real code structure — a glob that matches no files is a learning signal,
not a feature. The bootstrap flow seeds initial globs from the region map
in corpus/state/CODEBASE_STATE.md.
Pair it with corpus
A guide states what; the corpus states why. Corpus is loaded on demand — never auto-loaded — so a guide body can stay terse while the reasoning lives one hop away. Author the paired entry the generator stubbed:
# .charter/corpus/idioms/rails/migrations.md --- kind: corpus id: corpus/idioms/rails/migrations description: Why migrations stay small and reversible. --- An irreversible migration can't be rolled back cleanly in an incident. A migration that mixes schema + backfill locks tables for the duration of the backfill. Small, reversible migrations keep deploys safe and reviewable.
Project it to your hosts
Re-index and project. For Claude Code and other pointer-style hosts, the guide becomes a
rule shim under .claude/rules/, and idiom loading is gated through
corpus/state/GLOBS_INDEX.md. For Cursor, keystone project writes
a .cursor/rules/<id>.mdc with the globs: mirrored verbatim.
$ keystone index && keystone project ✓ .claude/rules/idioms-rails-migrations.md ✓ .cursor/rules/idioms-rails-migrations.mdc
Your first sensor
A sensor is a check that reacts to a signal or a host phase. It comes in two flavors: computational (a script → exit / HTTP status verdict) and inferential (an agent review → a structured verdict). You'll write one of each.
A computational sensor
Computational sensors run a command and turn its result into a verdict. A non-zero exit
(or a failing HTTP status) blocks the gate. Subscribe it to a host phase or a signal with
on:, and give it a run::
$ keystone new sensor lint
# .charter/sensors/lint.md --- kind: sensor id: lint description: Block the gate if the linter fails. mode: computational on: pre-verify run: golangci-lint run ./... --- Runs the project linter. A non-zero exit blocks verification.
Here on: pre-verify subscribes the sensor to a host phase. When that phase
fires, Keystone runs run: and reads its exit status — zero passes, non-zero
blocks. A sensor can equally subscribe to your own signal (see the next section).
An inferential sensor
Inferential sensors are reviews. Instead of a script, they're dispatched as an agent that
reads the diff and returns a structured verdict. Declare the shape with
returns::
# .charter/sensors/security-review.md --- kind: sensor id: security-review description: Flag OWASP-top-10 patterns in the current diff. mode: inferential on: pre-verify returns: verdict: pass | warn | block findings: list --- Read the diff. Report any injection, auth, or unsafe-default patterns. Return `block` on a clear vulnerability, `warn` on a smell, `pass` otherwise.
on:), different verdict source.
Signals
A signal is a Keystone framework event the host can't see — the extensible counterpart to
a host hook phase. You'll learn the closed set of host phases, how to define your own open
signals, and how reactions self-subscribe via on:.
Host phases vs. signals
There are two kinds of on: value, and the distinction is the whole idea:
- Host phases are a closed set bridged into the host —
PreToolUse,Stop, and friends. You subscribe to them; you don't invent them. - Signals are open. Any
on:value that isn't a host phase is a signal, so you define your own — declare them inkeystone.jsonundersignals:.
// keystone.json
{
"signals": [
"on-migration-added",
"on-release-cut"
]
}
Fire and list signals
Fire a signal by name; list the declared ones to discover what's available:
$ keystone signal fire on-migration-added $ keystone signal list
Reactions self-subscribe
There is no hook kind to route signals — reactions subscribe to themselves
via on:, exactly like a skill declares its triggers:. When a
signal fires, the subscribers react:
- sensor — runs a check → a verdict (exit / HTTP status); gates.
- tool — fires a side-effect (transport
cli | http | mcp | plugin). - agent — runs an inferential review → a structured
returns:.
So to react to your on-migration-added signal with a check, you write a
sensor with on: on-migration-added — no separate wiring. The primitive's own
frontmatter is the subscription.
# .charter/sensors/migration-guard.md --- kind: sensor id: migration-guard description: Check new migrations are reversible. mode: computational on: on-migration-added # self-subscribes to your signal run: script/check-reversible.sh ---
keystone hook fire is kept as an alias for
keystone signal fire, but there is no hook primitive — the
firing layer is the signal, and every reaction self-subscribes.
Policies & the cascade
Policies are how org discipline ships into every repo — vendored charter fragments, version-and-hash-pinned. You'll add one and learn the cascade rules that decide who wins.
A policy is a vendored charter fragment declared in
keystone.json. It's pinned by version, hash-verified, and drift-reset on
keystone verify. A policy can ship any primitive kind. Add one:
$ keystone policy add tacoda/tacoda-org@v2.0.0
The cascade
When the same item exists in more than one layer, the cascade decides which body loads. The rules, in order:
- Project wins by default. Your files at
.charter/<kind>/<id>override any policy's version of that item. - Nested policies refine outer ones. Among policies, a policy nested
deeper in
keystone.jsonrefines the outer policy it's nested in. strict:locks absolutely. A policy can mark an itemstrict— then nothing else, project or other policy, can override it. This is how a security team makes a rule non-negotiable.
Project always wins by default; nested policies refine; strict locks absolutely
under .charter/"] end subgraph policies ["vendored policies (read-only · pinned + hashed)"] outer["outer policy"] inner["nested policy
refines outer"] strict["strict item
locked absolutely"] end pg -->|wins by default| effective["effective charter"] outer --> effective inner -->|refines| outer strict -.->|overrides everything| effective classDef proj fill:#13161d,stroke:#4ade9a,color:#4ade9a classDef pol fill:#1a1e28,stroke:#60a5fa,color:#60a5fa classDef lock fill:#1a1e28,stroke:#f59e0b,color:#f59e0b class pg proj class outer,inner pol class strict lock
See the effective set
To see who actually wins after the cascade resolves, ask for the effective roster:
$ keystone charter show --effective # the post-cascade winning set $ keystone verify # cascade + policy-drift check
keystone verify with a DepthViolation.
Operating the charter
A charter is a living thing. You'll run the learn → synthesize → ratify flywheel, check coverage and conformance, explain any primitive, and pick a pacing mode.
The flywheel: learn → synthesize → ratify
The charter improves through a governed loop. You capture surprises, incidents, and review findings into the learning inbox; you synthesize accepted candidates into the right guide / corpus / sensor layer; you ratify the amendment (governance, provenance, hash-pinning). A parallel pruning wheel removes content that no longer earns its keep.
◴ Learning
.charter/learning/inbox/.◵ Pruning
.charter/archive/; re-index.Coverage and conformance
Two commands tell you whether the charter is healthy against the actual repo:
keystone charter coverage— which project files no guide governs ("uncharted territory").keystone charter conformance— a rubric across cascade, validity, pairing, and coverage, resolving toCONFORMANT,DRIFTING, orNON-CONFORMANT.
$ keystone charter coverage # files no guide governs $ keystone charter conformance # CONFORMANT | DRIFTING | NON-CONFORMANT
Explain any primitive
When you want to know how a primitive activates, what it links to, and where it projects —
plus any uncommitted changes — ask keystone explain:
$ keystone explain idioms/rails/migrations
Pacing modes
The charter runs at a pacing mode that controls how much autonomy the
agent has through the lifecycle — paired (tight human gates),
solo, and autopilot. Switch modes as trust in the charter grows;
the iron laws (sensitive files, dangerous actions, explicit acceptance criteria) never
loosen regardless of mode.
Next steps
You've installed Keystone, authored a charter, written a guide and two sensors, defined a signal, and learned the cascade. Here's where to go deeper.
Every primitive kind, its canonical frontmatter, path convention, cascade behavior, and projection targets — the port contracts — live in the reference. When you need the exact required fields for a kind, that's the page.
Keystone is open source under the MIT license. The 4.0.0 changelog records the charter
rename, the retirement of the hook kind, and the signals model. Contributions
welcome — see CONTRIBUTING.md for the local dev loop and the bar for adding a
primitive kind.
keystone init / index / project
Scaffold the charter, emit the descriptor index, regenerate host projections.
keystone verify / charter conformance
Cascade + policy-drift check; rubric across cascade / validity / pairing / coverage.
keystone signal fire | list
Fire or list the extensible framework events your reactions subscribe to via on:.
keystone explain / charter show
Explain any primitive; show the roster, --effective for the post-cascade winning set.