Deterministic policy and effects runtime for LLM tool calls.
Six-class effects taxonomy on state × boundary × reversibility axes. Ordinal precedence. Content-addressable receipts. Wires into Claude Code as a PreToolUse hook (Phase 3); usable anywhere you can shell out to a subprocess. Sibling project to M87-Spine-lite — see Porting Notes for the relationship.
You give it a tool call and a manifest of declared effects. It returns a deterministic decision: which effects fire, what the dominant class is, and a structured receipt you can replay by SHA. Built to wire into Claude Code as a PreToolUse hook; usable anywhere you can shell out to a subprocess.
The runtime is offline by design — no clocks, no randomness, no network, no LLM calls inside the runtime itself.
| Phase | Scope | Version | State |
|---|---|---|---|
| 1 | Scaffold, taxonomy, exceptions, CLI surface, CI matrix, docs | v0.1.0a0 |
Shipped 2026-05-08 |
| 2 | Manifest schema, classifier, Posture enum, parity + hypothesis tests | v0.2.0a0 |
Shipped 2026-05-08 |
| 3 | Posture transitions, Disposition, Receipt, hook adapter, full CLI, E2E | v0.3.0a0 |
Shipped 2026-05-09 |
See RECEIPTS.md for build progress and docs/history/phase-1.md for the Phase 1 narrative.
PyPI publish lands at the end of Phase 3. Until then, install from source:
git clone https://github.com/MacFall7/spine-lite-python
cd spine-lite-python
uv venv
uv sync --all-extras --devVerify:
uv run spine-lite version
# 0.1.0a0Need uv? curl -LsSf https://astral.sh/uv/install.sh | sh (Linux/macOS) or winget install astral-sh.uv (Windows).
from spine_lite import Effect, most_restrictive, SpineLiteError
# Collapse any non-empty effect set to its dominant class
most_restrictive({Effect.READ, Effect.NETWORK, Effect.WRITE})
# <Effect.NETWORK: 'network'>
# Precedence is total: DESTRUCTIVE > SPAWN > EXECUTE > NETWORK > WRITE > READ
most_restrictive({Effect.READ, Effect.WRITE, Effect.DESTRUCTIVE})
# <Effect.DESTRUCTIVE: 'destructive'>
# Every error in the package descends from SpineLiteError
issubclass(SpineLiteError, Exception) # TrueThe classifier and PreToolUse hook arrive in Phases 2 and 3. The taxonomy is the contract; everything else builds on it without changing it.
The closed six-class effects taxonomy:
| Effect | Meaning | Examples |
|---|---|---|
READ |
observation only | reading a file, listing a dir, querying without writing |
WRITE |
persistent state change to caller-owned storage | writing a file, updating a row, appending a log |
NETWORK |
outbound network call | HTTP request, DNS lookup, Slack message |
EXECUTE |
subprocess invocation, no fork | subprocess.run, os.system, shell pipeline |
SPAWN |
subprocess that may fork-and-detach | Popen with no wait, daemon launch, nohup |
DESTRUCTIVE |
irreversible state change | rm -rf, git push --force, dropping a table |
Ordered by precedence: DESTRUCTIVE > SPAWN > EXECUTE > NETWORK > WRITE > READ. most_restrictive() collapses any non-empty set to the highest class.
Same input → same output, every time. No clocks, no randomness, no I/O in the core.
- Not a model. No LLM calls happen inside the runtime.
- Not a sandbox. It classifies and decides; enforcement happens in the hook adapter.
- Not extensible at runtime. The taxonomy is closed by design — extending it is a project-level decision.
- Not a network library. There are zero network calls in the runtime, ever.
Full docs at https://macfall7.github.io/spine-lite-python/. Quick links:
- Getting Started — five-minute install + first decision.
- Concepts / Overview — mental model end to end.
- How-To / Use the API — practical patterns, today.
- How-To / Wire into Claude Code — operator runbook.
- Reference / API — auto-generated from docstrings.
- Reference / Glossary — vocabulary.
- Explanation / Architecture — why it's shaped this way.
- Explanation / Invariants — the rules nothing in this repo gets to break.
- Explanation / FAQ — common questions about the design.
spine-lite-python/
├── src/spine_lite/ # the runtime (5 pure modules + hook + cli + exceptions)
├── tests/ # unit + smoke tests, all offline
├── docs/ # mkdocs source — Diátaxis-structured
├── .github/workflows/ # CI matrix + Pages deploy
├── CLAUDE.md # repo governance for Claude Code sessions (≤ 150 lines)
├── CONTRIBUTING.md # contributor quick-start
├── CHANGELOG.md # what shipped in each version
├── RECEIPTS.md # append-only phase-day completion log
├── SECURITY.md # vulnerability reporting + trust model
├── pyproject.toml # hatchling build, deps pinned via uv.lock
└── uv.lock # tracked; CI cache keys against it
See CONTRIBUTING.md for the quick-start and docs/how-to/contribute.md for the long form. The closed effects taxonomy and the public API are non-negotiable; proposals to change either need a written rationale on an issue first.
See SECURITY.md. The runtime is deterministic and offline; trust questions live in your manifest.
This repo runs under M87 Studio with explicit phase boundaries. Authority split, halt conditions, and verification gates are in CLAUDE.md. Phase exit gates and receipts are in RECEIPTS.md.
MIT. Maintained by Mac McFall.