Sembl
Documentation

Getting started with Sembl

From install to your first verdict. Sembl is an accountability gate: you declare the bounds of a change, your agent does the work, and sembl verify checks the diff stayed inside them. No prior setup assumed.

Install

Sembl is a Python command-line tool. You need Python 3.10 or newer. The stable package is live on PyPI:

pip install sembl

That gives you the sembl command. Confirm it is there:

sembl --version

If you prefer an isolated CLI install, use uv:

uv tool install sembl
The gate needs no API key and no model - just Python and git. The optional generation beta needs a provider key and (optionally) the graph extra: pip install "sembl[graph-pipeline]". pip install sembl always gives you the latest stable release (0.2.0).

Verify a change

After your agent has edited the repo, point sembl verify at a bounds file and (optionally) the agent's own report:

sembl verify --wo-file bounds.json --report agent.json

Sembl reads the real git diff in the working tree, compares it to the bounds, cross-checks the report, and prints a single verdict with reasons:

sembl verify - BLOCK files changed ....... 7 out of scope ........ infra/deploy.yaml forbidden hits ...... none fabricated claims ... src/payments/refund.ts validation evidenced missing: pytest churn vs budget ..... 7 files > 6

The three verdicts:

  • PASS - the change stayed in bounds and the report checks out. Exit code 0.
  • WARN - soft signals (out-of-scope edit within tolerance, churn over budget, a test claimed passed with no evidence). Exit code 0, or 1 with --strict.
  • BLOCK - a hard breach: a forbidden-area edit or a fabricated file claim, or - in strict mode only, and only once scope_tolerance is exceeded - an out-of-scope edit. Exit code 1.
Add --json for machine-readable output and --strict to make WARN a failing exit code. Every check is deterministic - no model runs, so the verdict is identical on every machine.

The bounds contract

A bounds file is plain JSON. verify reads only five fields, so anything that can emit them can drive the gate:

{ "editable_paths": ["src/auth/", "src/api/routes/login.ts"], "forbidden_areas": ["migrations/", "infra/"], "churn_budget": { "max_files": 6, "max_lines": 200 }, "scope_tolerance": { "max_fraction": 0.25 } }
FieldMeaning
editable_pathsPath prefixes the change may touch. A changed file outside all of them is out-of-scope.
forbidden_areasPath prefixes the change must not touch. A hit here is a BLOCK.
churn_budget.max_filesSoft cap on changed files (WARN if exceeded). Optional.
churn_budget.max_linesSoft cap on added+deleted lines (WARN if exceeded). Optional.
scope_toleranceHow many out-of-scope edits are absorbed before they count toward the verdict (default: up to 25% of changed files) - real changes routinely touch a file or two no bound named. Set to {} for zero tolerance. Optional.

Paths are prefix-matched: src/auth/ matches src/auth/login.ts. Tests are always allowed alongside in-scope edits. Produce the file by hand, from Spec Kit (below), or with the generation beta - any extra keys are ignored, so a full work-order.json works unchanged.

The executor report

If you pass --report report.json, Sembl cross-checks the agent's own claims against the diff. It never trusts the report - it only catches contradictions:

  • Fabricated claims (BLOCK) - a file the report says it changed that the diff does not show. Recognized: files_modified, files_changed, files, changes: [{"file": "..."}].
  • Unevidenced validation (WARN) - a tests_passed: true / status: "passed" / checks: [...] claim with no backing exit_code: 0 or captured output.

The report is whatever JSON your agent or harness emits; the report is optional, and scope/forbidden/churn checks run with or without it.

Gate in CI

Run verify --strict as a step on every agent-authored PR. A forbidden edit or a fabricated claim fails the build before a human reads a line:

# GitHub Actions step - name: sembl verify run: | pip install sembl sembl verify --wo-file bounds.json --report agent.json --strict

Because the check is deterministic, the verdict is reproducible and auditable - the same diff and bounds always yield the same result.

Use it from an agent: MCP & Skills

Agents can call the gate directly - no shell, no clone, no API key. The fastest path is zero-install: paste this into your MCP client's config (Claude Code .mcp.json, Cursor, Windsurf, …) and uvx fetches and runs it on demand:

{ "mcpServers": { "sembl": { "command": "uvx", "args": ["--from", "sembl[mcp]", "sembl-mcp"] } } }

Or install it and run the server yourself over stdio:

pip install "sembl[mcp]" sembl-mcp

Either way it mirrors the whole CLI:

  • verify_change - the gate. Pass a unified diff (no checkout needed) plus editable_paths / forbidden_areas (or a bounds_file) and an optional report of what the actor claims it did. Returns the verdict and per-check findings.
  • bounds_from_spec - derive bounds from a Spec Kit tasks.md, a preset, or a custom config.
  • list_presets, doctor - the declarative presets, and deterministic repo diagnostics.
  • clarify_task, generate_work_order beta - the generation half, for completeness.

Main agent verifies sub-agent

The general case for delegating safely: the sub-agent declares the files it may touch and reports what it did; the orchestrator gates the result instead of trusting the self-report.

// verify_change arguments { "diff": "<the sub-agent's patch>", "editable_paths": ["src/auth/"], "forbidden_areas": ["migrations/"], "report": { "changed_files": ["src/auth/login.ts"], "tests_passed": true } }

This catches a sub-agent that edits outside its declared files, touches a forbidden area, claims a file it never changed, or says "tests passed" with no evidence - deterministically, with no second model in the loop.

Drop-in Agent Skills

Copy the skills in skills/ into .claude/skills/:

  • sembl-verify-subagent - verify a sub-agent's work before accepting it.
  • sembl-setup-bounds - create a bounds file for a repo.
  • sembl-gate-pr - gate a PR or local diff before merge.

Bounds from GitHub Spec Kit

Spec Kit plans what to build and writes specs/<feature>/tasks.md, where each task already names the exact file paths it will touch. sembl bounds turns those into a bounds file:

sembl bounds --spec-kit specs/001-login --out bounds.json # or point straight at the file sembl bounds --spec-kit specs/001-login/tasks.md --out bounds.json

It collects the task file paths into editable_paths and derives a grounded max_files budget. forbidden_areas is left empty for you to fill with whatever the spec declared off-limits (migrations, infra, generated code). Then verify as usual. Tessl, Kiro, or a hand-written file work the same way.

Use a spec-driven tool to decide the plan; use Sembl to verify the agent stayed inside it. Sembl sits downstream of the planner and is independent of the agent.

Draft a Work Order beta

Optional and unproven as an outcome-improver. Our own testing did not show that a generated Work Order makes an agent produce better code than a one-line task. Generation is kept as a convenient way to produce a bounds file and an executor prompt - if you already plan with Spec Kit, use that instead.

Open a terminal inside a repo and describe the task in plain words:

sembl generate --task "fix the login redirect bug" --provider openai

Sembl reads the repo, asks a model to write a Work Order, grounds every path against your real code, and writes a folder under .sembl/:

.sembl/work-orders/wo-<project>-<timestamp>-<slug>/ work-order.md # human-readable executor-prompt.md # paste into your agent validation-plan.md # run after the agent finishes work-order.json # this is what verify --wo-file reads

Browse them with sembl list, sembl show, and sembl show --file executor-prompt. The work-order.json doubles as a bounds file for verify.

Set an API key beta

Generation asks a language model to write the Work Order, so it needs one provider API key. (The gate itself needs none.) Set the key as an environment variable; Sembl reads it automatically.

ProviderEnvironment variable--providerDefault model
OpenAIOPENAI_API_KEYopenaigpt-4o
AnthropicANTHROPIC_API_KEYanthropicclaude-sonnet-4-6
Google GeminiGEMINI_API_KEYgeminigemini-2.5-flash
NVIDIA NIMNVIDIA_API_KEYnvidiamistralai/mistral-medium-3.5-128b
OpenRouterOPENROUTER_API_KEYopenroutermoonshotai/kimi-k2
TokenRouterTOKENROUTER_API_KEYtokenrouterMiniMax-M3
Ollama (local)none - OLLAMA_HOST optionalollamaqwen2.5-coder:7b
Claude Code CLInone - uses your claude loginclaude-cliwhatever your CLI login defaults to

Windows - PowerShell

# just this terminal $env:OPENAI_API_KEY = "sk-your-key" # or save it for every future terminal setx OPENAI_API_KEY "sk-your-key"
After setx, open a new terminal - it doesn't change the one you're in.

macOS / Linux - bash or zsh

# just this terminal export OPENAI_API_KEY="sk-your-key" # or save it permanently echo 'export OPENAI_API_KEY="sk-your-key"' >> ~/.zshrc

Providers beta

All providers work the same way - change --provider and the matching key. A few notes:

  • OpenAI and Anthropic are the most reliable at producing clean, structured Work Orders.
  • Gemini is fast and inexpensive for everyday tasks.
  • NVIDIA NIM, OpenRouter, and TokenRouter reach open and catalog models; pick a strong instruction-follower for JSON discipline. Pass the id with --model, e.g. moonshotai/kimi-k2.
  • Ollama runs a model locally - no key, no rate limits, offline. Install Ollama, ollama pull qwen2.5-coder:7b, then --provider ollama.
  • Claude Code CLI drives your existing claude login instead of a raw API key - no key to manage, uses whatever subscription/auth the CLI already has.
sembl generate --task "..." --provider anthropic --model claude-sonnet-4-6

Graph pipeline beta

Generation can use graph context - a structural map of your repo built by Graphify and code-review-graph - to ground the scope it drafts. It is best-effort and off the critical path.

pip install "sembl[graph-pipeline]" # build the graphs once graphify update C:\path\to\repo --no-cluster code-review-graph build --repo C:\path\to\repo --data-dir C:\path\to\repo-crg-data --skip-flows # then generate against them $env:CRG_DATA_DIR = "C:\path\to\repo-crg-data" sembl generate --repo C:\path\to\repo --task "..." --graph-mode required

Run sembl doctor to see what's installed and built and the exact command to fix each gap (--fix installs missing tools; --json for tooling). Control graph use with --graph-mode auto|required|off and rebuild with --refresh-graph.

How verify works

The gate is deliberately small and deterministic. Given a bounds file and (optionally) a report, it:

  1. Reads the diff - modified, staged, and untracked files vs HEAD, normalized so an editor's line-ending rewrites don't count as edits.
  2. Classifies each file - in editable_paths, in forbidden_areas, or out-of-scope. Tests count as in-scope.
  3. Cross-checks the report - claimed files not in the diff are fabrications; "passed" claims with no evidence are flagged.
  4. Measures churn - file and line counts against the budget.
  5. Rolls up a verdict - BLOCK for a hard breach, WARN for a soft signal, PASS when clean.

No language model runs at any step, and Sembl makes no judgement about whether the code is good - only whether the change is objectively inside the lines it was given. That is the whole claim, and it's why the verdict is reproducible and free.

Troubleshooting

verify says everything is out-of-scope

Your editable_paths are empty or too narrow. Check the bounds file. If you built it with sembl bounds, confirm the Spec Kit tasks.md actually names file paths - prose tasks produce no paths.

verify exits 1 but I only see warnings

You passed --strict, which turns WARN into a failing exit code for CI. Drop --strict to let warnings pass.

My fabrication / validation checks do nothing

Those only run when you pass --report. Sembl recognizes common report shapes (files_modified, changes[], tests_passed, a checks[] list) - match one of them.

A formatter dirtied every file and verify flags them

Sembl ignores line-ending-only rewrites already. If real reformatting changed many files, that's a genuine churn signal - tighten the agent's task or raise max_files deliberately.

Generation: "No API key found" / quota / model errors

These come from the optional generation beta, not the gate. Set the matching provider key (on Windows, open a new terminal after setx), check billing/quota, or pass a model you have access to with --model.

"Graph context required but unavailable"

You used --graph-mode required but no graph context was available. Run sembl doctor to see what's missing, build the graphs, or switch to --graph-mode auto.