CausalMesh
A research-grade experimental platform for separating information from enforcement in distributed LLM coding agents.
Install
pip install causalmesh # SDK, policy gate, CLI, offline verifier
pip install 'causalmesh[server]' # adds `causalmesh serve`
causalmesh init # write a starter causalmesh.yaml (one role, agent_a)
causalmesh policy check --agent agent_a --kind path --name src/app.py
Put the gate in front of your own agent's tools:
from causalmesh import CausalMesh, Tool
mesh = CausalMesh.from_config("causalmesh.yaml")
mesh.register_tool(Tool(name="write_file", handler=write_file, mutating=True,
resource_args=["path"]))
result = mesh.execute(agent_id="agent_a", tool="write_file",
arguments={"path": "src/app.py", "content": "..."})
A refused call never reaches the handler, and the refusal is signed evidence that
causalmesh verify checks offline. The full local journey, with no key and no model,
is examples/cli_demo/.
The experiment harness (four arms, the task suite, experiment run) is not in the
wheel: git clone the repository for it. Pre-alpha. Every run so far used a
deterministic mock agent; nothing here has been run against a hosted model, so nothing
here says how real coding agents behave. See docs/backlog.md.
The research question
How much of the coordination benefit in distributed LLM coding agents comes from information, and how much comes from enforcement?
When multiple coding agents work on a shared codebase, they interfere: they make stale reads, duplicate each other's work, and overwrite each other's changes. Systems that fix this usually do two things at once — they tell agents what peers are doing, and they prevent agents from conflicting. Because these arrive together, their individual contributions are unknown.
CausalMesh separates them experimentally. The contribution is measurement, not the invention of a new coordination mechanism.
Experimental design
Four arms, manipulating exactly two factors:
| Arm | Information | Enforcement | Description |
|---|---|---|---|
| C0 | none | none | Fully isolated agents, no peer visibility |
| C1 | unbounded transcript | none | Agents see a full conversational transcript |
| C2 | bounded structured metadata | none | Agents see typed events describing what changed |
| C3 | bounded structured metadata | write claims | Metadata plus enforced exclusive write claims |
The contrasts that answer the question:
C0 → C2 information benefit
C1 → C2 information representation / coordination cost
C2 → C3 enforcement benefit
C1 → C3 complete coordinated system
Everything other than information and enforcement is held constant: task, model, decoding parameters, agent roles, repository fixture, tool semantics, success criteria, randomization protocol. Anything that cannot be held constant is recorded and treated as a potential confound.
Architecture
┌───────────────────────────┐
│ Experiment DB │
└─────────────┬─────────────┘
│
┌─────────────▼─────────────┐
│ Experiment Scheduler │
└─────────────┬─────────────┘
┌────────────────────────┼────────────────────────┐
┌─────▼─────┐ ┌─────▼─────┐ ┌─────▼─────┐
│ Agent A │ │ Agent B │ │ Agent N │
└─────┬─────┘ └─────┬─────┘ └─────┬─────┘
└───────────────┬────────┴───────────────┬────────┘
│ Lifecycle Hooks
┌────────▼────────┐
│ Coordination │
│ Boundary │
└────────┬────────┘
┌─────▼─────┐
│ Relay │
└─────┬─────┘
┌───────────────┼────────────────┐
┌─────▼─────┐ ┌─────▼─────┐ ┌─────▼─────┐
│ Event Log │ │ Claims │ │ Fan-out │
└─────┬─────┘ └─────┬─────┘ └─────┬─────┘
└───────────────┼────────────────┘
┌──────▼──────┐
│ Observer │
└──────┬──────┘
┌───────▼────────┐
│ Offline Verify │
└───────┬────────┘
┌────────▼─────────┐
│ Research Dataset │
└────────┬─────────┘
┌────────▼─────────┐
│ Analysis Engine │
└────────┬─────────┘
┌──────────────┼──────────────┐
plots tables report
Dependencies run one way only. Analysis never mutates runtime state, the verifier never depends on the relay being online, and the observer is strictly read-only.
Measurement integrity
Every coordination event is appended to a cryptographic chain:
h_i = SHA256( h_(i-1) || canonical(event_i) )
Each event is signed with a per-actor Ed25519 key, so attribution is verifiable rather than asserted. A standalone offline verifier validates the chain from nothing but the log, the public keys, and optional anchors — the relay can be completely offline.
The adversarial battery tests what the chain actually detects:
| Attack | Expected result |
|---|---|
| Event mutation | detected |
| Event insertion | detected |
| Event deletion | detected |
| Event reordering | detected |
| Signature substitution | detected |
| Wrong-actor attribution | detected |
| Duplicate replay | detected |
| Suffix truncation | undetected without an anchor, detected with one |
That last row is a real limitation, documented rather than hidden.
Status
Substrate complete and exercised; statistics and reporting are not built. On 2026-09-30, pytest --collect-only found
1751 tests. On 2026-10-12, python scripts/check.py (lint, format, types on the
enforced modules, the security scan and the whole suite) passed on one Windows 11,
Python 3.13.5 machine: 2918 passed, 0 failed, 15 skipped. The skips need a POSIX
file system, an optional package such as CrewAI, or an API key. That is one machine
on one day, and there is no CI to corroborate it: the workflow was removed rather
than paid for (see docs/backlog.md). Linux and macOS have not been run. Re-run
python scripts/check.py rather than trust this line.
The build follows a vertical slice first: a single scenario driven end-to-end through every subsystem, executed under all four arms, before breadth is added. No subsystem is considered complete until it participates in a real executable path.
| Subsystem | State |
|---|---|
| Event protocol, canonicalization | ✅ property-tested |
| Ed25519 identity, hash chain | ✅ |
| Offline verifier + adversarial battery | ✅ 8 attack classes |
| Claim registry, leases | ✅ exclusivity tested across threads and processes on one filesystem; no fencing, no automatic renewal, no multi-path acquire (see backlog) |
| Relay: sequence, sign, chain, fan-out | ✅ |
| Isolation, 11 tools, lifecycle hooks | ✅ leakage-tested |
| Channels, relevance, information window | ✅ |
| Script-driven agent | ✅ |
| Task system + empirical §37 gate | ✅ 4 authored tasks, one per §35 family; verdicts come from scripted mock agents |
| Scheduler, state machine, run index | ✅ resume-tested |
| Model adapters (OpenAI, Anthropic, local) | ✅ tested over real HTTP against a local stand-in server; no hosted model has been called |
| Policy plane (contract, gate, ledger), checkpoints, identity keys | ✅ unit and integration tested |
Developer SDK, tool gateway, causalmesh serve (FastAPI) |
✅ tested; Docker path re-verified 2026-09-30 |
Framework adapters: Generic, OpenAI function calling, Anthropic tool_use, LangChain, LangGraph, CrewAI |
✅ tested with scripted payloads and fakes; no hosted model has driven any of them, and CrewAI has not run against the real package |
| Metrics (cost accounting, four contrasts), provenance, manifests, CLI | ✅ descriptive only |
Multi-rater kappa (causalmesh agreement), CooperBench loader |
✅ the loader imports all 652 real CooperBench pairs (the real §37 gate on 3 pairs: NOT SENSITIVE, mock agents); the kappa code is tested on synthetic fixtures only and no human has coded a run |
| Statistics (inference), analytical module, reporting | ⬜ not built (src/causalmesh/reporting is empty; analysis/ holds only agreement.py) |
Not yet done, and stated plainly: no hosted model has been run (no key was
available, and spending money is the operator's decision), no human rater has
coded a run, no real CooperBench task has been run with anything but a mock
agent replaying gold patches, and there is no inferential statistics or
reporting layer. See docs/backlog.md,
which records every gap as a tracked item with its reason.
What is and is not enforced today (2026-10-12; per-claim evidence in docs/claims-ledger.md):
- Ownership refusals: the example Claude Code hook refuses
WriteandEditon a path another role owns. It has been run only against synthesized input, never a realclaudesession. - Lease (claim) refusals: SDK sessions given
claims=, and experiment runs without acausalmesh.yaml(in contract-gated runs the lease key does not match; see backlog), on one filesystem. The example hook does not consult leases. - Not covered:
Bash,MultiEdit,NotebookEdit; agents on different machines; fencing tokens and automatic lease renewal (a lease that lapses mid-write is not stopped). - Evidence: a deterministic mock agent only, on Windows only, with no CI.
Measured results
A real pilot collection — 2 tasks × 4 arms × 3 repetitions = 24 runs,
executed on this machine, 150 s wall clock, by a deterministic mock agent
(no language model was involved). Nothing below is illustrative. The raw runs
are not tracked in git; data/manifests/pilot_landing.manifest.json records
24 artifact hashes, but causalmesh audit cannot re-check them from a fresh
checkout, and 35 tests skip for that reason.
| Arm | Success | Mean coordination bytes | Denials | Divergences | Chains verified |
|---|---|---|---|---|---|
| C0 | 0/6 | 0 | 0 | 6 | 6/6 |
| C1 | 6/6 | 361 | 0 | 0 | 6/6 |
| C2 | 6/6 | 196 | 0 | 0 | 6/6 |
| C3 | 3/6 | 196 | 3 | 0 | 6/6 |
Reading it:
- C0 fails every run. Isolated agents make exactly the stale-read and clobber errors the tasks induce. Zero coordination bytes, and a single unique context digest across all turns — proof that nothing entered, not an assertion that nothing should have.
- Information alone recovers everything on both tasks. C1 and C2 are 6/6.
- C1 costs 84% more than C2 for identical outcomes. That is the representational overhead, measured.
- C2 and C3 spend identically (196 bytes), as they must: they differ by enforcement alone, and any information difference would confound the contrast.
- C3 splits 3/3 and 0/3. Where ownership is disjoint it matches C2. Where the path is genuinely contested, enforcement prevents the clobber and blocks legitimate work — the file needs two writers. That is §74's trade-off measured rather than assumed to be an improvement.
- All 24 chains verify offline, rebuilt from each run's derived seed.
One pilot estimates a direction, not an effect size. The confirmatory design (1920 runs) is built and gated: it commits to a preregistered analysis this project has not written.
📋 Implementation plan
→ docs/PLAN.md — the full implementation plan.
It covers the architecture, the honest security model, the 17-step build sequence, the verification procedure, and the explicit non-goals of the current slice.
Usage
To integrate CausalMesh into an existing AI coding agent, use the SDK to register your tools and route executions through the policy gateway.
Security-sensitive resources should be explicitly declared rather than inferred from arbitrary tool arguments. CausalMesh extracts them deterministically.
from causalmesh import CausalMesh, Tool
# 1. Load the policy contract and initialize the mesh
mesh = CausalMesh.from_config("causalmesh.yaml")
# 2. Register your agent's tools
mesh.register_tool(
Tool(
name="write_file",
handler=my_agent.write_file_handler,
kind="filesystem",
mutating=True,
resource_args=["path"], # Explicitly declare protected resources
)
)
# 3. Route actions through the Mesh
result = mesh.execute(
agent_id="coding-agent",
tool="write_file",
arguments={"path": "src/main.py", "content": "print('hello')"}
)
if result.allowed:
print(f"Action succeeded! Return value: {result.value}")
else:
print(f"Action denied: {result.describe()}")
Tools are gated unless registered with mutating=False. Only agents declared
under roles (or passed as agents=) can call execute; any other agent raises
UnknownAgentError before anything runs, because its calls could not be signed.
Only arm C3 refuses. On C0-C2 a call C3 would refuse runs and reports
result.would_refuse. Pass seed= or declare identity.keys for a
reproducible signed chain. docs/backlog.md lists what the SDK does not do yet.
Adapters put the same gate in front of a framework's tool loop: OpenAI function
calling, Anthropic tool_use, LangChain, LangGraph and CrewAI, all through
CausalMesh.execute. They are tested with scripted payloads and fakes; no hosted
model has driven any of them, and CrewAI has not run against the real package.
from causalmesh.integrations import AnthropicAdapter
adapter = AnthropicAdapter(mesh, agent_id="coding-agent")
# pass adapter.get_tools() as `tools=` to the Messages API, then answer the
# model's tool_use blocks:
reply = adapter.handle_message(assistant_message) # a user message, or None
mesh.export("evidence/") writes run.json and keyring.json so that
causalmesh verify --run evidence/ can check the session; causalmesh serve --tools
loads your tool registrations into the HTTP server, and --ssl-certfile/--ssl-keyfile
serve it over TLS (see docs/security.md for what each does and does not protect).
For async agents, use await mesh.execute_async(...) to ensure non-blocking execution while maintaining policy enforcement boundaries. A denied async action is never awaited.
See examples/phase1_basic_agent.py and examples/phase1_async_agent.py for complete working examples.
To see the whole path locally (contract, gateway, an allowed call, a denied call
whose handler never runs, and evidence you can verify), follow
examples/cli_demo/. It needs no API key and no
model, and python examples/cli_demo/run_demo.py runs it unattended. It is a
demonstration of a pre-alpha SDK. It was written before causalmesh serve --tools,
TLS and CausalMesh.export existed, so it serves its tool from a short script and
holds its evidence in memory.
Run the suite with pytest. The slow integration tests execute real
subprocesses; pytest -m "not slow" skips them.
The CLI
Install the package, then run a collection and verify what it produced:
pip install -e .
causalmesh doctor # environment health check
causalmesh task list # the task suite
causalmesh task validate # structural validation (§36)
causalmesh experiment plan --profile smoke # a run schedule, executed nothing
causalmesh experiment run --profile smoke # execute; resumable and idempotent
causalmesh experiment status --latest # per-arm outcomes
causalmesh verify --all # offline chain verification
causalmesh analyze --latest # costs and the four contrasts
causalmesh manifest write --latest # SHA-256 over every artifact
causalmesh audit --all # evidence present, unchanged, valid
causalmesh watch --latest # live view of a collection
causalmesh connectors list # what this machine actually has
causalmesh policy init # write a starter causalmesh.yaml
causalmesh policy show # the contract, as the gate reads it
causalmesh policy check --agent buyer \
--kind spend --name evil.io --amount 2.40
The buyer example refuses only once the buyer role in the starter contract is
uncommented; the contract states exceptions, so an agent it does not name is
unrestricted and the check answers ALLOW.
policy check asks the gate whether an action would be allowed, without
running an agent or spending anything, and exits 0 for allowed and 1 for
refused. It records nothing: a denial count that included questions would
measure how often the gate was consulted rather than how much work was
blocked. See docs/policy.md.
watch is read-only: it opens the run index mode=ro, so attaching it to
a collection executing in another terminal cannot interfere with it. That
is possible because progress lives in the index rather than in the
executing process — the same property that makes experiment run
resumable.
Every command takes --json for scripting, and exits 0 on success, 1
on a negative result, 2 on a bad invocation and 3 when it could not run
— so a caller can branch without parsing output.
causalmesh verify --run <directory> verifies a single exported run with no
repository and no relay present, which is the §82 clean-room check.
analyze is descriptive: it reports what the runs did, per arm and per
task, and makes no claim that a difference is distinguishable from noise.
The statistics layer that would support such a claim is not built.
audit is what makes the traceability commitment checkable rather than
asserted: it confirms every recorded artifact is present and unchanged, and
re-verifies every chain rather than trusting the manifest — a manifest
proves the bytes have not changed, not that they were ever valid.
Manifests are tracked in git while data/raw/ is not, so a published
figure stays traceable to its run after the raw directory is gone.
Connecting real coding agents
causalmesh connectors list probes this machine for installed agents and
infrastructure. Every probe invokes the tool rather than trusting PATH,
and reports one of three outcomes — available, degraded (installed,
executable, not answering) or absent.
This build includes headless drivers for claude and codex; neither has been
run against a real hosted model yet (see Status). A run driven by a real
agent is stamped reproducible: false in its artifact: hosted models
are nondeterministic and the agent's own version moves under it. The chain
still signs and verifies — it records what happened, which is a different
property from being repeatable. Such a run also costs money, so nothing
runs an agent unless you name it.
Not yet built: report. The analysis and reporting subsystems are
empty — causalmesh doctor says so, and
docs/backlog.md records why.
Design commitments
- No fabricated results. Ever. Not results, not benchmarks, not model outputs.
- Raw data is immutable. Once written,
data/raw/is never modified by analysis. - Failed runs are evidence. They are recorded with full context, never silently discarded.
- No overclaiming. Security properties are stated only where a passing test establishes them; the threat model documents what is not protected.
- Full traceability. Every reported number traces from the claim, through the analysis, through processed data, through the run, through the signed event chain, back to the exact task, configuration, model and commit that produced it.
Repository layout
configs/ experiment, agent, model, relay, claim and task configuration
src/ the causalmesh package
tasks/ declarative task definitions
fixtures/ repository fixtures used by tasks
data/ raw (immutable), processed, manifests, exports
artifacts/ chains, metrics, plots, tables, reports, certificates
tests/ unit, integration, property, adversarial, failure, reproducibility
docs/ architecture, protocol, events, claims, security, analysis
frontend/ the website: landing page and documentation (Next.js)
Documentation
| Document | What it covers |
|---|---|
docs/IDEA.md |
What this is and why, for a deployment reader |
docs/PLAN.md |
The full build sequence |
docs/security.md |
What the hash chain and signatures establish, and what they do not |
docs/isolation.md |
The three-layer isolation model and its limits |
docs/adapters.md |
Model adapters, and what has not been executed |
docs/scheduler.md |
The run loop, state machine and randomization |
docs/claims.md |
Write leases and enforcement |
docs/policy.md |
The contract, the gate, and resources of every kind |
docs/FRAMEWORK-PLAN.md |
Where this is going, and the research behind it |
docs/tasks.md |
Task definitions and the §37 sensitivity gate |
docs/backlog.md |
Every known gap, with its reason |
Vercel deployment
The website is a single Next.js application in frontend/: the
landing page is served at / and the documentation at /docs. Import the
repository with the root directory left as .; the root
vercel.json points Vercel at that app.
Vercel detects the framework by looking for next in the root
package.json, which in a workspace layout is not where the dependency
actually lives. It is declared in both: the real one in frontend/, and a
matching entry at the root so detection succeeds. Keep the two versions in
step — a stale root entry makes Vercel resolve a version the app does not
use.
npm install
npm run dev # http://localhost:3000 — landing at /, docs at /docs
npm run build
The landing page and the docs were previously two separate applications stitched together by Vercel routing rules. They are now one build, which is why no route configuration is needed to move between them. Use Node.js 20 or newer.
Requirements
Python 3.11+ and Git. The analysis stack uses numpy, scipy, pandas, statsmodels and matplotlib; the runtime core deliberately stays lighter, leaning on the standard library and cryptography for Ed25519. No Kafka, Redis, Kubernetes, Prometheus or Grafana.
License
MIT — see LICENSE.
Metadata
Release files for causalmesh 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| causalmesh-0.1.0.tar.gz | 934.8 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| causalmesh-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 1.4 MB
Release files / causalmesh-0.1.0.tar.gz
| Download URL | causalmesh-0.1.0.tar.gz |
|---|---|
| Size | 934.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
f86a5b94da26523e4673184696e17d21dbce764144a7a9e2e5c9c54b58deb2ee
|
|
BLAKE2b-256 checksum How to use checksums |
8abc743d10a37b8897b3b3d0e48e86b5fa251d4aea1908290e5c172646776ecb
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.5
|
Release files / causalmesh-0.1.0-py3-none-any.whl
| Download URL | causalmesh-0.1.0-py3-none-any.whl |
|---|---|
| Size | 443.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
d7cad4bec24113580871f744c65e8baa8d65cda3dd6d1e1e026f221edfeb622e
|
|
BLAKE2b-256 checksum How to use checksums |
0054aebac4aa99aae37d1443f7cc267462993d42de6f6ee174665d3f4a8dbcb2
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.5
|