Crew Kit was a rulebook: eight short documents and a set of templates, the method we use to run this company on agents with a person signing off what matters. The rules that failed because someone forgot stayed failures, because a rule in a document cannot notice anything. v0.3 gives the rulebook an engine that reads the files a crew already keeps and returns a verdict.
Our position: the interesting part of this release is not the engine. It is that two of its checks now tell you what they cannot do, in the same line where they say pass — and both of those sentences came out of reviewing the release rather than using it.
Four commands
health — is each agent still moving? State freshness, the weekly status, overdue backlog items, handoffs nobody answered.
lint — do the files match the record layer: the manifest, the header blocks, the contracts, the node profiles.
gates — was the kill line written before the test started? Pre-registration reduced to a comparison between two dates.
verify — is the decision log valid against its schema, and is it consistently chained.
Three verdicts, and one of them is unknown
Checks usually have two outcomes, which forces every gap into one of them. A check that cannot see something reports it as fine, because fine is the only word available. So the kit has three verdicts, and unknown is never folded into pass or fail.
The engine cannot see money leaving an account, a message being sent, or something being posted in public. It reads files. So the check for rule slips comes back unknown — "the engine cannot see money, sends or public posts" — rather than green. A gap stays visible instead of being rounded to the nearest comfortable colour. This is the central design choice in the release and everything else follows from it.
The mutual contract
A role used to declare what it delivers. That is half a contract, and the missing half is the one that explains why an agent is quiet: an agent starved of input and an agent that has stopped working look identical from outside. v0.3 records both halves — I deliver and I am owed — so those two states stop being the same state.
A contract that names no oracle is invalid, and its role may not act autonomously under it. If nothing can tell you whether the work was right, nothing should be running it unattended.
Capability placement
Work declares what it needs. Nodes declare what they have. A program matches them. The point is the direction: a permission becomes a grant rather than a self-description, and a thing that needs a capability cannot simply assert it has one. One node stays the default, so nothing here is required of someone running their whole crew on one machine.
The worked example is amber and red on purpose
Run the engine against the example crew that ships with the kit and it exits non-zero and prints failing rows. That is deliberate. A green example teaches that green is the normal state, which is the single most expensive thing a reader could learn from us. The example shows you what the output looks like when something is actually wrong, on the first run, before you have anything of your own at stake.
What it will not do
Standard library only, no dependencies, no model anywhere in the path. The same input and the same --asof date produce byte-identical output, which is what makes a difference between two runs mean something. It reads; it never writes, fixes or scaffolds. Verified on Python 3.9 and 3.13, with 82 tests.
It does not read your code, so it cannot tell you an import ban is in place. It cannot see spend, sends or posts. Those come back unknown, by design, forever — until something that can see them reports in.
Two checks that say what they cannot do
This is the part we would point at. Reviewing the release — not using it, reviewing it — turned up two checks whose names promised more than they measured.
The decision log is hash-chained, and the chain check used to report chain intact. But the chain is self-anchored: each hash covers the record it sits on, and the first record's predecessor is null. So rewriting every record and re-chaining from scratch produces a chain that verifies. We measured it rather than reasoning about it: replace every decision's text, escalate its kind, fabricate its source, re-chain — and both the schema check and the chain check pass. The check now says:
pass chain — 2 record(s), internally consistent - a re-chained rewrite passes too, so this is not tamper-evidenceThe second is pre-registration, which our own method calls the easiest rule to fudge. The check compares the date the kill line was written against the date the test started — and both are self-reported fields in a file the author writes. Writing them in one sitting passes. Backdating one passes. It is fudgeable by typing. So:
pass pre_registered — … both self-reported in this file, so this is the order claimed, not the order provedNeither algorithm changed. A hash chain with no external anchor is evidence against carelessness and accident, which is most real failure, and it is no evidence at all against intent. What changed is that it no longer invites the stronger reading at the moment someone is reading it. Tamper-evidence needs an anchor the author does not control — a signature over the head record, or a head published somewhere the author cannot rewrite.
Three of the kit's checks now name their own limit where they report. They are exactly the three whose names promise more than they measure. We would not add a fourth unless it earned it the same way: a check that disclaims something it genuinely does measure is noise, and output that disclaims everything reads as a kit that does not trust itself.
What to expect next
Direction rather than dates — our own kill-line rule applies to our own roadmap, and a date we have not committed to is a claim we cannot keep.
Take the pre-registration date from the commit that introduced the line rather than from a field, so it becomes a date the author does not control, measured from a record that already exists. And make records name the check that wrote them: when every guard writes its own distinct field, "which check refused?" becomes a lookup rather than a judgement — answerable later, by someone without today's context, for free.
Also read: Brief Nº8 — An error that agrees with you does not get caught →