enforcement, not prose

Stop asking your agent to behave.
Enforce it.

agent-contract installs a .agent/ contract into any repo. Each task gets a scope — and every out-of-scope write is blocked by a shell check that exits non-zero, before it touches your code. Any agent, any model.

view source ↗

then agent-contract init in any repo · or one-shot with npx @semeton/agent-contract init

2,800+ downloads 21 on GitHub 0 runtime deps 14+ stacks
payroll-service — agent session
the gap

Prose is a request. A contract is a constraint.

CLAUDE.md, AGENTS.md and .cursorrules all rely on the model choosing to cooperate. There is no enforcement layer underneath them — so nothing actually stops an agent from touching what it shouldn't.

asks

Prose & rules files

You write "please don't touch the auth module." The model skims it, the text drifts from the code, and on a long task it quietly refactors three services anyway. You find out in the diff.

enforces

The .agent/ contract

A PreToolUse hook reads the active role and blocks any write outside its declared scope. Fail-closed — no role declared, no source writes. The agent can't even widen its own scope: the role files are off-limits in-session.

the loop · any agent, any model

Five gates between a prompt and your codebase.

Each is a shell script that exits non-zero on a violation. The orchestrator reads exit codes, not promises.

01

Boot

The agent reads the contract before it reads your source: latest handoff → manifest.yaml → codebase map → its role.

read-only
02

Declare scope

pre-generate.sh validates the task spec has what it needs, then writes the active role — which arms the enforcement hook.

gate
03

Enforce live

Every Write / Edit is checked against the role's allowed paths. Out-of-scope writes are blocked before they land — not flagged after.

blocks writes
04

Verify

post-generate.sh runs the right tools for your stack — eslint + tsc + jest, or phpstan + pint + phpunit — plus your convention gates.

gate
05

Hand off

A Stop hook writes a handoff note on any turn with changes, so the next session boots with the decisions instead of starting over.

memory
~10 minutes per service

Install once. Init per repo.

Installing globally puts the CLI on your PATH, so updates are one npm i -g away. Then init detects your stack, writes the contract and installs the hooks — idempotent, re-run any time. Prefer not to install globally? npx @semeton/agent-contract init runs it in one shot.

open source · PRs welcome

Adding a language is one file.

Every stack is a single detector that returns evidence and a score. The highest score wins. Write one, and an entire ecosystem gets a contract.

  • Zero deps to learn — pure stdlib, no build step, no framework.
  • Good first issues — new detectors, a read-back YAML parser, the orchestrator loop.
  • Tested end to end — one smoke test gates every publish.
// lib/detect/detectors/elixir.js module.exports = { async detect(ctx) { const mix = await ctx.read("mix.exs"); if (!mix) return null; return { language: "elixir", framework: mix.includes(":phoenix") ? "phoenix" : "none", orm: "ecto", // evidence → score → resolver picks the winner score: 90, }; }, };

// what it is

  • An enforcement layer that scopes each task and blocks out-of-scope writes by exit code.
  • Stack-aware: it runs the correct linter, type-checker and tests for your project.
  • Tool-agnostic shims for Claude Code, Cursor, Copilot and the API.

// what it isn't (yet)

  • Live blocking runs inside Claude Code's hooks today; elsewhere, wire post-generate.sh into a pre-commit hook or CI.
  • It enforces scope, not logic correctness — that's what the tester role and coverage gate are for.
  • Convention files need ~30 min of tuning per service. One-time.