snodo
AI-SDLC Protocol Engine — a governance layer for AI-assisted software development.
You define a protocol — a YAML specification of operational modes, validators and constraints — and snodo executes tasks through it. Every task passes validation gates before and after execution; a disagreement policy decides whether the work proceeds, escalates to a human, or halts. What lands is what your standards admitted, and the hash-chained audit log says why.
The coder is interchangeable and separate from the judge. An in-process LLM client, a host CLI running on your own subscription, or a containerised server all converge on the same gate and the same merge path.
Preprint: Specifying AI-SDLC Processes: A Protocol Language for Human-Agent Boundaries — arXiv:2606.20615.
Project status
Actively-developed research implementation (beta).
| Code | ~39,400 lines across 5 packages (snodo-core, snodo-tools, snodo-foundation, snodo-engine, snodo-mcp) |
| Complexity | average cyclomatic complexity A (4.9) |
| Lint / architecture | ruff clean; package layering enforced in CI by import-linter |
| Python | 3.12 and 3.13 (CI matrix) |
The enforcement invariants — token integrity, capability boundaries, non-overridable blockers, audit completeness — are verified by property-based tests over randomized inputs.
Install
pip install snodo
From source (a uv workspace):
git clone https://github.com/snodo-dev/snodo.git
cd snodo
uv sync --all-extras
You need Python 3.12+, a model for the validators (always routed through
LiteLLM), and a coder, which need not be the same thing. Anthropic, OpenAI,
Google, OpenRouter, DeepSeek and Cloudflare Workers AI have built-in provider
configuration; any other OpenAI-compatible endpoint works by declaring a
provider block with base_url and litellm_provider: openai — Ollama, vLLM,
LM Studio, llama.cpp, self-hosted gateways. A local endpoint that needs no
key is not asked for one. --coder opencode-cli and --coder agy delegate
code generation to a CLI authenticated against your own subscription, so no
provider key is spent writing code. --mock needs nothing at all.
Quickstart
snodo init --template solo # writes .snodo/protocol.yml
snodo config add anthropic sk-ant-... # store a provider key
snodo config set model claude-sonnet-4 # default model
snodo ready # is this project set up for the protocol?
snodo run "implement a hello world function"
A key already exported in your environment (ANTHROPIC_API_KEY and friends) is
auto-detected. To try the loop without spending anything, add --mock.
snodo ready is worth running first on an existing repository: it checks,
deterministically and without an LLM, whether every artefact the protocol
demands is committed — decision records, a resolvable test command, coder
configs, paths cited in criteria — and scores what is missing by how cheap it
is to fix. Task worktrees only see HEAD, so "present on disk" is not enough.
Protocol language
| Concept | Description |
|---|---|
| Mode | An operational stage with a declared set of tools and validators. Disjoint tool sets enforce separation of duties — a producer cannot merge, a reviewer cannot edit. |
| Validator | An evaluation applied to a task. Has a validator_type (security, architecture, quality, conventions, planning…), an evaluation_phase (pre_execute or post_execute), and criteria — LLM prompt strings, or tooling config for deterministic checks. |
| Disagreement policy | How validator results combine: unanimous, majority, quorum (default 2/3), or any. |
| Severity | A result is pass, warn or blocker. A blocker halts regardless of policy. |
| Constraint | A rule enforced over execution artifacts through a predicate framework — deterministic, not judged. |
| Transition | A declarative event-to-mode mapping documenting intended handoffs. |
Seven templates ship with snodo:
| Template | Modes | For |
|---|---|---|
solo |
producer | A single developer with full access |
team |
producer, reviewer, planner | Three-mode team workflow |
2+n |
producer, reviewer | Paper reference configuration |
greenfield |
plan, decide, scaffold, build | A new project, from decisions to first code |
intent |
producer | Intent-driven authoring |
feature-warden |
producer | Feature development with a strict review gate |
bugfix-surgeon |
producer | Narrow, surgical defect work |
Coders
The coder writes; snodo governs, gates, and records. Which one you pick does not change what is enforced.
--coder |
Mechanism | Needs | Auth |
|---|---|---|---|
litellm (default) |
In-process completions via LiteLLM | built-in | provider API keys |
opencode-cli |
Host opencode run |
opencode on PATH |
opencode auth login |
agy |
Antigravity CLI (agy -p) |
agy on PATH |
agy login |
opencode |
OpenCode server in Docker over HTTP | Docker, opencode:latest |
container env |
mock |
Deterministic stub | nothing | none |
Three things about them are worth knowing up front:
-msets the judging model, not the coder's. Validators and the classifier run on it. Host CLIs keep their own model catalogs; to pin a coder's model, namespace it —--coder agy --model agy/gemini-2.5-pro.- In-place coders own their commit.
opencode,opencode-cliandagyedit the worktree directly and commit, so post-execute validators judge the exact change. Any attempt to touch.snodo/halts as a blocker (ADR 027). - Selection order:
--mock, then--coder, then a mode'scoder:field, then a model prefix (agy/,opencode-cli/,claude,gpt…), thenlitellm.
To add one, subclass SubprocessCoderAdapter, set binary, model_prefix and
install_hint, implement _build_argv, and register it in CODER_REGISTRY
(snodo/coders/__init__.py) — that alone exposes it to --coder, enables
prefix routing, and enrolls it in the adapter conformance suite.
Commands
snodo <command> --help is authoritative; full reference at
docs.snodo.dev.
init |
Scaffold .snodo/ from a template |
run |
Execute a task, a plan (--plan), or a single wave (--wave). --background, --resume, --retry, --from-pr, --interactive, --no-isolation |
ready |
Score method-scaffolding readiness against the protocol |
plan |
list, status, create, validate, add-wave, add-task, run, delete |
status / mode |
Active session and mode; mode change to switch |
session |
list, show, new, switch, delete, prune |
authorize |
Adjudicate escalated disagreements and set_model proposals |
validate |
Check the protocol against the well-formedness rules |
audit verify |
Verify the hash chain |
job / logs / meta |
Background jobs: list, status, logs, wait, cancel; log streaming; usage |
task / worktree |
Task branches and the git worktrees used for isolation |
recon |
Fan out read-only agents to answer a question about the codebase |
models / config |
Model discovery; keys and settings |
serve |
Run the protocol as an MCP server (stdio or SSE) |
cloud |
connect, disconnect, status for audit sync |
dashboard |
TUI (snop) |
agent / sandbox / install / uninstall |
Agent memory; Docker sandbox; Claude Desktop MCP entries |
Plans are authored, not generated: plan create scaffolds one empty wave, and
you add waves and tasks (ids are <wave>.<seq>_<name>, e.g. 1.1_models) or
edit plan.yml directly. A plan is re-verified on every load. See
docs/runbooks/hand-authored-plan.md.
Architecture
- Mode-based capability separation. Each mode declares its tools. WF1 well-formedness forbids an approval-conferring tool from appearing in two modes, so separation of duties is structural rather than advisory.
- Validator gates with disagreement policies.
pre_executevalidators run before the coder,post_executeafter. Results combine under the declared policy; a blocker halts immediately and cannot be overridden. - JWT validation tokens. Agreement issues a signed token, and mutating MCP tools require a valid one — validation is non-overridable at the capability boundary, not just in the engine.
- Hash-chained audit log. Append-only, tamper-evident, and the record of every governance decision and verification. Optionally synced to snodo cloud.
- Session resumability. State is checkpointed under
$SNODO_HOME/sessions/. Resume withsnodo run --resume <session_id>; escalations are adjudicated withsnodo authorizeand the session continues. - Coder adapter pattern. The backend sits behind a
CoderAdapterinterface, so a new one plugs in without touching the engine. - LangGraph execution engine. The protocol compiles to a
StateGraphbuilt dynamically from the YAML — any arrangement of modes and validators. - Modular packages.
snodo-core(kernel: config, predicates, sandbox) →snodo-tools(workspace, git, shell, code hosts) →snodo-foundation(infrastructure, compiler, protocols) →snodo-engine(engine, validators, coders) →snodo-mcp(MCP servers, recon, jobs), with the rootsnodopackage as CLI and dashboard. Layering is enforced in CI byimport-linter.
Configuration
Configuration lives in ~/.snodo/config.yml ($SNODO_HOME overrides the
location). Manage it with snodo config rather than editing by hand:
snodo config add anthropic sk-ant-...
snodo config set model deepseek/deepseek-v4
snodo config show
model: deepseek/deepseek-v4 # default for all roles
llm:
coder:
max_tokens: 64000
temperature: 0.1
validator:
model: openai/@cf/google/gemma-4 # role-specific override
max_tokens: 25000
recon:
num_agents: 2
engine:
max_subtask_depth: 3
max_session_age_days: 30
token_ttl_seconds: 1200
providers:
anthropic:
api_key: sk-ant-...
api_key_env: ANTHROPIC_API_KEY # injected at runtime when a matching model runs
ollama:
base_url: https://ollama.com/v1
api_key_env: OLLAMA_API_KEY
litellm_provider: openai # route ollama/<model> through the OpenAI protocol
cloud:
api_url: https://api.snodo.dev
sync_enabled: true
litellm_provider: openai is what makes an arbitrary compatible endpoint work:
snodo rewrites ollama/<model> to openai/<model> and sends it to base_url.
Omit api_key_env for a local server that needs no key.
Read from the environment, never stored in the config file: SNODO_HOME,
SNODO_TOKEN_SECRET (HMAC secret for token signing; random per process by
default), GITHUB_TOKEN (for --from-pr), and any <PROVIDER>_API_KEY.
Research
Prifti, Y. (2026). Specifying AI-SDLC Processes: A Protocol Language for Human-Agent Boundaries. arXiv:2606.20615. https://doi.org/10.48550/arXiv.2606.20615
@misc{prifti2026snodo,
title = {Specifying AI-SDLC Processes: A Protocol Language for Human-Agent Boundaries},
author = {Prifti, Ylli},
year = {2026},
eprint = {2606.20615},
archivePrefix = {arXiv},
doi = {10.48550/arXiv.2606.20615},
url = {https://arxiv.org/abs/2606.20615}
}
Empirical studies live in studies/:
uv sync --extra studies
make studies
Architecture decisions are recorded in docs/decisions/.
Contributing
See CONTRIBUTING.md.
License
Copyright (C) 2026 The snodo Authors. Licensed under the Apache License, Version 2.0 — see LICENSE.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file snodo-0.8.2.tar.gz.
File metadata
- Download URL: snodo-0.8.2.tar.gz
- Upload date:
- Size: 161.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a2df3eda0c8cfa730260de92e17ec66c23a8595a470c47abc7969a8d36ca5257
|
|
| MD5 |
3d5eadc698c422451185ca3c01c2e305
|
|
| BLAKE2b-256 |
f44b141fc666bddf0bc8015b0bfd173fba94decf166e6a0569a8d3cea7d54be5
|
Provenance
The following attestation bundles were made for snodo-0.8.2.tar.gz:
Publisher:
release.yml on snodo-dev/snodo
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
snodo-0.8.2.tar.gz -
Subject digest:
a2df3eda0c8cfa730260de92e17ec66c23a8595a470c47abc7969a8d36ca5257 - Sigstore transparency entry: 2774899168
- Sigstore integration time:
-
Permalink:
snodo-dev/snodo@697d2528598c2782e19c16daf27acdf83ff28953 -
Branch / Tag:
refs/tags/v0.8.2 - Owner: https://github.com/snodo-dev
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@697d2528598c2782e19c16daf27acdf83ff28953 -
Trigger Event:
push
-
Statement type:
File details
Details for the file snodo-0.8.2-py3-none-any.whl.
File metadata
- Download URL: snodo-0.8.2-py3-none-any.whl
- Upload date:
- Size: 174.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a3ef5f2e50be6e7e49c255dff24da6cb610f705e495972c470197e3b707373fb
|
|
| MD5 |
40a7999ebfa49cdd260261edcd381c53
|
|
| BLAKE2b-256 |
bc8af810f1489fe0631138222b7d4fa9c4abd3300e2f7388abf6e622fd785bf3
|
Provenance
The following attestation bundles were made for snodo-0.8.2-py3-none-any.whl:
Publisher:
release.yml on snodo-dev/snodo
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
snodo-0.8.2-py3-none-any.whl -
Subject digest:
a3ef5f2e50be6e7e49c255dff24da6cb610f705e495972c470197e3b707373fb - Sigstore transparency entry: 2774899424
- Sigstore integration time:
-
Permalink:
snodo-dev/snodo@697d2528598c2782e19c16daf27acdf83ff28953 -
Branch / Tag:
refs/tags/v0.8.2 - Owner: https://github.com/snodo-dev
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@697d2528598c2782e19c16daf27acdf83ff28953 -
Trigger Event:
push
-
Statement type: