the failure mode
You have felt this one. A project outgrows a single session. You split the work, and for a while it goes well. Then the seams start to move.
One session names the field id. Another, three days later,
is confident it was always orderId. A schema picks up a
required field and nothing downstream notices. Someone summarizes the API into a
fresh session and the summary is subtly wrong, so the code built on it is subtly
wrong. Every one of these is individually trivial. They are also invisible until
integration, which is the most expensive possible moment to find them.
The usual response is to try to give the model more memory: longer context, better
summaries, a CLAUDE.md that grows until nobody reads it.
This treats forgetting as the bug. It isn't. It is the operating condition.
Human teams solved this a long time ago, and not by remembering harder. They solved it with interfaces, versioning, and a build that fails. The reason a type error stops you is not that a colleague noticed. It is that a machine checked.
the premise
Sharding starts by conceding the thing everyone else argues with: LLM inconsistency is a given, not something to hope away.
So the system never asks a session to hold the whole product in its head. It makes the shared truth external, small, and mechanically enforced. Two rules follow, and almost everything else is a consequence of them:
- A shard can only ever see its own slice plus a read-only copy of the contract. Not by convention. By hook, at the tool call.
- Any divergence from the contract is caught by deterministic tooling at a gate, the same way a type error blocks a build, rather than by an agent noticing.
conductor workspace
+-------------------------------------------+
| contract/ |
| frozen . versioned |
+-------------------------------------------+
| | |
read-only read-only read-only
slice slice slice
v v v
+-----------+ +-----------+ +-----------+
| orders | | gateway | | ... |
+-----------+ +-----------+ +-----------+
X--------------X--------------X
no shard may read another shard's internals
Everything lives on disk, so sessions become genuinely disposable. Close them all, open fresh ones, and they re-orient from files rather than memory. A session that remembers nothing is not a degraded session here. It is the expected case.
the thesis
This is a claim under test, and the repository exists to test it:
N independently-driven, isolated shards, coupled only through a frozen contract and gated mechanically, will snap together into an integrable deliverable at each phase - despite each session being individually forgetful.
the substrate
Three artifacts do all the work, and all three are just files:
project-root/ # conductor workspace contract/ # THE frozen truth; read-only to shards interfaces/ # APIs, signatures, endpoints, events schemas/ # shared data shapes conventions.json # invariants: naming, versioning phases.yaml # phases + acceptance criteria VERSION # frozen while a phase is open shards/ <shard-name>/ SHARD.md # charter: PROVIDES vs CONSUMES CLAUDE.md # sandbox guardrails surface/ # declared surface (what gets diffed) ...shard code... .sharding/ manifest.yaml # the shard graph
1. the contract
Small, external, frozen, versioned. The only thing a shard is permitted to couple to. It holds interfaces, shared data shapes, invariants like error and naming conventions, and the definition of what each phase must prove.
2. the manifest
The graph. For each shard: where it lives, which contract slices it provides, which it consumes, and which surface adapter it uses. This is what tells the checker what each shard is on the hook for.
contractVersion: v1
currentPhase: phase-1
shards:
orders:
dir: shards/orders
adapter: identity
provides: [OrderAPI, Order]
consumes: []
gateway:
dir: shards/gateway
adapter: identity
provides: []
consumes: [OrderAPI, Order]
3. the declared surface
Each shard commits a machine-checkable statement of what it exposes. The checker diffs that statement structurally against the contract slice the manifest says it provides. Structurally, not textually: it compares declared shapes, which is what makes the result deterministic regardless of how the model chose to write the code.
Adapters turn a stack's real output into that structural surface, so the diff engine stays language-agnostic. A stack with no adapter yet degrades honestly to gate-only rather than reporting a green check it cannot justify.
three mechanisms
Each layer is deterministic and none of them depends on anyone remembering anything.
-
isolate
A
PreToolUsehook confines a session opened insideshards/<name>/to that directory plus the read-only contract. Reaching into a sibling shard's internals is denied at the tool call, before the read happens. Writing to the contract from a shard is denied outright.This is what makes "it can only see its slice" a fact rather than an instruction. A shard cannot accidentally couple to another shard's implementation, because it cannot look at it.
-
diff
/shard-checkextracts the shard's declared surface and diffs it against its contract slice, on the provide side and the consume side both. Findings carry a precise location, not a vague complaint.A
Stophook runs the same check when a session tries to wrap up, so a drifted shard cannot declare itself done. That matters more than it sounds: the most common way this fails without tooling is a confident completion claim that is quietly wrong. -
gate
/shard-phase-checkruns the drift check across every participating shard, then runs the phase's acceptance criteria. A phase closes only when all provide-side diffs are clean, all consume-side checks are clean, every participating shard has acknowledged the frozen contract version, and the acceptance suite is green.This is the deterministic definition of "done and it snaps together." Nothing advances on an assurance.
drift vs change
Here is the idea the rest of the system is built around. Drift and change are the same mechanical event seen from opposite sides. A shard diverging from the frozen contract is drift, and it is illegal. The conductor deliberately altering the contract is change, and it is legal, versioned, and loud.
The system tells them apart by two things it can actually check: who is allowed to
touch contract/, which the isolation hook enforces, and
whether the change was made deliberately at the conductor level.
drift, caught precisely
A shard renames one field in its response. One field, in one place. Here is what the engine says about it:
node dist/cli.mjs check orders
{
"shard": "orders",
"clean": false,
"findings": [
{
"slice": "OrderAPI",
"kind": "missing-field",
"location": "placeOrder.response.id",
"expected": "string"
},
{
"slice": "OrderAPI",
"kind": "extra-field",
"location": "placeOrder.response.orderId",
"actual": "string"
}
],
"contractVersion": "v1",
"verifiedAgainst": "v1",
"versionStale": false
}
exit 1
Not "something looks off in the orders shard." The exact path, what the contract expected, what the shard actually declared. And because it exits non-zero, it drops straight into a script or a CI step without anyone reading it.
change, with a computed blast radius
Now the other direction. The conductor adds a required
currency field to the Order schema
and bumps the contract. This is a legitimate change. The question that normally gets
lost - who does this break? - stops being a question anyone has to answer
from memory:
node dist/cli.mjs status
{
"contractVersion": "v2",
"currentPhase": "phase-1",
"shards": [
{
"shard": "orders",
"clean": false,
"findings": [
{
"slice": "Order",
"kind": "missing-field",
"location": "Order.currency",
"expected": "string"
}
],
"contractVersion": "v2",
"verifiedAgainst": "v1",
"versionStale": true
},
{
"shard": "gateway",
"clean": false,
"findings": [
{
"slice": "Order",
"kind": "missing-field",
"location": "Order.currency",
"expected": "string"
}
],
"contractVersion": "v2",
"verifiedAgainst": "v1",
"versionStale": true
}
],
"blastRadius": [
"orders",
"gateway"
],
"staleShards": [
"orders",
"gateway"
]
}
exit 1
Both the provider and the consumer light up, each with the specific field that moved under them. No shard can quietly stay on the old shape, because the next check and the Stop hook will both catch it.
the change a diff cannot see
Structural diffing has a blind spot, and it is worth naming rather than glossing. Some breaking changes leave every declared shape identical: a narrowed enum, a tightened validation rule, a redefined unit on an existing number, a reinterpreted error code. Diff the surfaces and they match perfectly. Nothing is caught.
So the contract version is a second, independent channel. Every shard records the version it was last verified against, and a bump marks it stale until someone explicitly acknowledges it:
node dist/cli.mjs check orders
{
"shard": "orders",
"clean": true,
"findings": [],
"contractVersion": "v2",
"verifiedAgainst": "v1",
"versionStale": true
}
exit 0
Note the exit code. Staleness is deliberately not drift: a bump should not interrupt every shard mid-task, and the everyday check stays green. It blocks in exactly one place, the phase gate, because a phase cannot claim to be provably integrable while a participating shard was never checked against the version the phase froze.
node dist/cli.mjs phase-check
{
"phase": "phase-1",
"shardsClean": true,
"versionsAcknowledged": false,
"staleShards": [
"orders",
"gateway"
],
"acceptancePassed": true,
"passed": false,
"findings": [],
"acceptanceOutput": ""
}
exit 1
Nothing drifted and the acceptance suite passes. The phase is still blocked, and correctly so.
Clearing it is a deliberate act, not a side effect. A clean structural diff never re-blesses a shard on its own, because that is precisely the case this channel exists to catch. The shard reviews what actually changed, then records it - from inside its own sandbox:
cd shards/orders && node ../../dist/cli.mjs ack
{
"shard": "orders",
"acknowledged": true,
"verifiedAgainst": "v2"
}
exit 0
The shard acknowledges itself, and only itself. The change this channel exists to
catch is one a shape diff cannot see, so judging it means reading that shard's
implementation - which the conductor deliberately does not hold. A conductor-side
stamp would be a blind one. The record lands in the shard's own
surface/ACKNOWLEDGED, so nothing outside the sandbox is
written. And the testimony is never taken as proof: the gate still measures every
shard for real drift independently, so an acknowledgment cannot launder a structural
problem into a green phase.
That is the whole trick. The thing a growing project loses track of first becomes a query you can run.
the demo
The repository ships a two-shard demo that exercises the real engine end to end.
orders provides an Order API. gateway
consumes it. They are never allowed to see each other.
-
scaffold the workspace
/shard-initat the root createscontract/,shards/, and the manifest, and records which surface adapter this project uses. -
author and freeze the contract
/shard-contractdefines theOrderAPIinterface and theOrderschema, sets phase one's acceptance criteria, and freezes at v1. From here the target does not move. -
register the shards
/shard-new ordersand/shard-new gatewaycreate each directory with its charter and sandbox guardrails, and record in the manifest what each one provides and consumes. -
build the provider, in a session that knows nothing
Open a fresh session in
shards/orders/. SessionStart injects its charter, its contract slices, and the isolation rule. It builds the service and emits its surface. When it tries to finish, the Stop hook runs the check.check clean against OrderAPI v1 - completion allowed
-
build the consumer, and watch it get stopped
A fresh session in
shards/gateway/builds againstOrderandOrderAPIas seen only through the contract. It tries to peek at howordersactually implemented things.PreToolUse denied - shards/orders/ is outside this shard's sandbox
This is the step that usually surprises people. The consumer is not trusted to resist a shortcut. It is prevented from taking one.
-
close the phase
Back at the root, the gate runs every shard's diff plus the phase's acceptance criteria.
node dist/cli.mjs phase-check{ "phase": "phase-1", "shardsClean": true, "versionsAcknowledged": true, "staleShards": [], "acceptancePassed": true, "passed": true, "findings": [], "acceptanceOutput": "" }exit 0
-
stress it
Change the contract on purpose and re-run
status. That is the blast radius shown above: both shards flagged, each with the exact field that moved. Fix, re-gate, continue.Step seven is the real proof. It is the moment a human organization starts losing threads, and the moment this system starts computing instead of remembering.
what's proven
This is a proof of concept and it is worth being straight about which parts are demonstrated and which are not.
demonstrated
- isolation is real
- A shard session cannot read another shard's internals or write the contract. The denials fire at the tool call.
- drift cannot be declared done
- The Stop hook blocks a completion claim from a shard that diverged, with a precise finding.
- blast radius is computed
- A contract change surfaces every affected shard and the exact field, rather than relying on anyone tracking it.
- invisible changes still block
- A version bump that leaves every shape identical marks each shard stale and holds the phase gate until it is explicitly acknowledged.
- the gate means something
- A phase goes green only when every diff is clean and the acceptance suite actually passes.
- sessions are disposable
- Close everything, reopen fresh, and the on-disk truth fully re-orients each session.
not yet
- a standalone orchestrator
- Something that actively drives the shard sessions is the natural next step and is deliberately out of scope here.
- broad adapter coverage
- The engine ships identity and JSON Schema adapters. Other stacks need an adapter to get surface diffing, and degrade to gate-only until then.
- a large-N trial
- The mechanism is proven on two shards. The thesis is about N, and N is where it gets interesting.
install
From the marketplace, inside Claude Code:
/plugin marketplace add robworks-code/robworks-claude-code-plugins/plugin install sharding@robworks-claude-code-plugins
Then reload plugins, or restart the session.
the commands
Once installed the commands are namespaced, so
/shard-status becomes
/sharding:shard-status.
/shard-init- scaffold the conductor workspace. One time./shard-contract- author or amend the contract, then freeze and bump. Conductor only./shard-new <name>- register a shard with its provides and consumes./shard-check [name]- diff a shard's surface against the contract./shard-ack- run inside a shard: acknowledge it against the current contract version./shard-phase-check- the gate./shard-status- the graph, the phase, per-shard drift, blast radius.
without the plugin
Every operation is also a plain CLI, so the same workflow runs in CI or any editor. Each command exits non-zero on drift or a failing gate.
npm installnpm run cli -- check <shard>npm run cli -- statusnpm run cli -- phase-check
The only runtime dependency is yaml. Everything else is dev
tooling.