Repository navigation
Answers that carry their proof. A spec can now require evidence for what
its harness says. The new provability: block sets a floor on a four-rung
ladder. At cited every claim names a source. At grounded the run read
that source, and it says what the claim says. At checked a tool the run
called computed the value, and a pure tool is run again to confirm it. At
proven a proof checker accepted a proof of the claim. Before an answer is
served, a deterministic gate checks each claim against what the run
recorded. An answer that falls short is retried with a note naming what
failed, handed to a stronger model, served with a visible ✗, or refused, as
the spec's on_fail says, and the verdict travels with the answer as a proof
bundle. The gate is off until a spec turns it on: a spec without the block
runs as before, apart from the changes under Before you upgrade and the
fixes under Fixed. Specs stay version: 2, with nothing to migrate. The
design is in
design/0.8.0-provability-release.md.
0.8.0 also ships the 0.7.1 hardening, which makes the 0.7.0 tools safe to
grant. 0.7.1 was never tagged, so its notes are part of this entry: under
each heading below the provability release's entries come first, and the
hardening's follow as they were written. An audit of 0.7.0 found that
its new tools did less than the release notes said, and could do more than a
spec allowed. A permission rule could be dodged by spelling a call another
way. A tool could write through a symbolic link planted in the workspace, or
hang on a crafted file or pattern. The model could choose which environment
variable a tool sent as a credential. And tool_config never reached many of
the tools that read it, while shapes other than cli could not use the new
tools at all. This release fixes what the audit confirmed, apart from the
items under Known limitations. Most specs need no change. To upgrade: use
Bun 1.3.14 or later; run crewhaus lint and crewhaus compile --strict on
each spec, which point out most rules, tool lists and tool_config blocks
this release reads differently; read Before you upgrade under Changed;
then recompile your bundles, since a bundle uses the packages of the CLI that
built it.
Added
Provability
- The
provability:block, on cli, channel, managed and research specs.
provability: groundedsets the floor and takes every default. The object
form is strict, so a misspelt key fails the build, and the error names each
key that is wrong. Besidelevelit takeson_fail(retryby default,
halt,note, orescalate, which needsagent.model_pool),
max_retries(default 1),threshold(the share of claims that must meet
the floor, default 1.0),scope(session, orturnfor evidence from the
current turn only),assumptions(alloworforbid) and
max_assumptions,render(footer,noneorjson), and four
sub-blocks:sources(host-globallowanddenylists,bibliographic
to accept free-text references, andresolve: headfor a reachability
check),grounded(coverage, default 0.8;window_tokens, default 60;
independent, the distinct origins each claim needs, default 1),
execution(the tools whose results count as evidence, by default
[all-compute, all-verify, -goldenUpdate], the read-only computation and
verification tools;replay;max_replays) andformal(checkers, by
default[builtin, smt], withbuiltinadded when a list leaves it out; an
imagefor the lean4 checker; the Lean pre-scan'sforbidlist;
timeout_ms). An optionaljudge.modelmay excuse a paraphrase the
grounded check missed, and nothing else; it is metered as a judge under
budget.judge_share, andjudge.allow_self_judge: trueaccepts a judge
that is also a serving model. On research,on_faildefaults tonote,
andretryorescalatelower tonotewith a warning. A crew spec
accepts the block with anaccepted-but-unwiredwarning, and every other
shape rejects it.level: offcompiles to the same bytes as no block at
all. - The gate. It runs where the
evaluation:judge runs, once a turn's tool
calls are done, and before the judge, so an attempt the gate sends back
costs no judge call (the judge grades only an answer the gate accepted, or
one served afternoteor once its retries ran out); each gate keeps its
own retry cap. A claim is a sentence, not a question, that carries a
citation marker, states a number or has three content words, outside code
and title headings; a definition line is not one, but text it shows after
its source (a link title aside) is read as a claim citing that definition.
Each claim must bind, through a[n]marker and a[n]: <source>
definition line, to evidence at or above the floor: a URL, adoi:or a
workspace path,tool:<toolUseId>, orproof:prf_…. A claim may instead
rest on a declared assumption,[A1]: assume: …, which is never evidence,
is left out of the score and is always counted. An answer in which no claim
is found passes as vacuous only when it carries no citation marker and no
figure outside a question. On a fail,retryre-runs the turn with a note
listing each unmet claim, what it cited and why that did not count (never
fetched text), ending with an instruction to runProvabilityPreflighton
the next draft; oncemax_retriesis spent the last answer is served with
✗.haltstops the run at the first failing verdict, with the new failure
classprovabilityand exit code 37 (EXIT_CODES.provability); on the
cli, where an answer streams as it is written, the refused answer has
already printed, and its ✗ footer follows it.escalatere-runs the turn
on a stronger pool candidate and shares the cascade'smax_escalations. A
verifier that errors does not stop a healthy run: the answer stands, with
anunverifiedbundle that says so. - What each rung checks. Higher-rung evidence meets a lower floor.
cited: every marker is defined, and every source can be located (an
http(s) URL on a hostsourcesallows and not a private address, adoi:,
or a workspace file that exists).grounded: the cited page or file was
fetched or read in this session, as its log records it (child sessions
included, so a compaction does not lose it), and the claim's words sit
within one window of it, a quotation verbatim; a page is found under the
address asked for and the one a redirect reached.checked: the claim cites
tool:<toolUseId>of a successful call to a tool onexecution.allow, its
result contains the claim's figures, and a pure tool is run again with the
logged input to confirm the result.proven: the claim citesproof:prf_…,
a proof a checker accepted in this run whose recorded claim matches the
sentence; the bundle lists the formal statement beside the prose, the
hypotheses the proof rests on, which count as assumptions, and any axiom
beyond the checker's standard ones. - A proof bundle with every gated answer. One bundle, every surface: a
footer line after the answer on the cli and on a channel reply
(── provability · grounded ✓ · 6/6 claims · 3 sources · 0 assumptions,
then the bundle's path; a fail addsunmet: [2] [5]), aprooffield on
the managed gateway's run results, a## Provabilitysection in a research
report, aprovability_gradedline in the trace and the session log, a
hash-chainedprovability_evaluatedrecord when the audit sink is wired,
and a JSON file at.crewhaus/proofs/<spec>/<sessionId>/turn-<n>.json, mode
0600. A retried turn keeps every pass, the k-th asturn-<n>.<k>.json, and
the last is the turn's standing bundle.crewhaus state backupcarries them
with the rest of.crewhaus/, except on the managed shape: there each turn
runs under its tenant, and the bundles live under
<CREWHAUS_TENANTS_ROOT>/<tenant>/proofs/<spec>/(default
/tmp/crewhaus-tenants, which state backup does not reach and an OS tmp
cleaner may delete); the daemon says so at boot when the variable is unset.
PointCREWHAUS_TENANTS_ROOTinside.crewhaus(for example
.crewhaus/tenants) to keep them, or back that root up separately.
render: noneleaves the footer out andrender: jsonprints the bundle.
Where the reader watches the answer stream (the REPL and
crewhaus run --prompt), a rejected attempt gets its ✗ before the retry,
and a final message that states nothing ("Done.") has the turn's printed
text graded in its place. A line the model
writes in the footer's shape is shown marked
(model-written, not the verdict). A source the run wrote itself before
reading it back still grounds, but the bundle names the call that wrote it
and the footer reads1 source (1 written by this run). - Three proof tools,
ProofCheckers,ProofCheckand
ProvabilityPreflight, in a newproofcategory that rolls up under
all-safety. A gated cli, channel or managed spec gets them without listing
them:ProvabilityPreflightat every floor, the other two atprovenor
when itsformal.checkersis not the default.ProofCheckerslists the
checkers this harness can use, each with its statement language.
ProofCheck({ checker, claim, statement, proof }) runs a checker, saves
the artifact, accepted or rejected, under
.crewhaus/proofs/<spec>/artifacts/, publishes aproof_checkedevent, and
returns theprf_id to cite, or the reason and a counterexample.
ProvabilityPreflightruns the gate's own verifier over a draft and returns
what the gate would say, without sending HEAD requests, replaying tools or
asking the judge.ProofCheckers,ProvabilityPreflightand
ProofCheck(builtin)are allowed by default, so aprovenharness works
headless;ProofCheckwithsmtorlean4starts a container and asks. - Three proof checkers.
builtinis a small certificate checker that
needs no sandbox: exact rational arithmetic, propositional connectives and
quantifiers over explicit finite domains, checked step by step from a
certificate that names each step's rule, orevalfor a statement it can
evaluate outright. It runs in a worker thread, under a deadline of at most
10 seconds.smthands SMT-LIB 2 to Z3: the hypotheses and the negated
goal, whereunsatproves the claim and a model is the counterexample.
lean4runs Lean 4 withInitandStd(pointformal.imageat a
Mathlib-equipped image when a proof needs more); a pre-scan refuses the
formal.forbidtokens (sorry,axiom,native_decideby default) before
Lean runs. A proof resting onsorryAxis rejected, and so is one that
trusts the compiler (decide +native,bv_decide) whilenative_decideis
forbidden; any other axiom beyondpropext,Classical.choiceand
Quot.soundis recorded. The two sandbox checkers run only in a container
sandbox, never on thenoopbackend, with no network and a read-only root,
in curated images published asghcr.io/crewhaus/sandbox-z3:0.8.0(Z3
4.16.0) andghcr.io/crewhaus/sandbox-lean4:0.8.0(Lean 4.34.1):
distroless, non-root, with no shell, built from sha256-checked official
releases. A newsandbox-imagesrelease job builds each image, runs it the
way the sandbox does, and only then pushes it;
scripts/build-sandbox-images.tsbuilds them locally. crewhaus provability show | check | audit | checkers | prune.show
renders a persisted bundle in long form: the latest, or the one--session,
--turnand--passname, with--jsonfor the file as stored.
check <answer.md> --level <rung>runs the gate offline over any document,
against an--evidencedirectory or a session's log, and exits 1 on a fail,
so a generated report can be gated in CI;--reportgrades a research
report as its own gate did, and--tenantreads a managed tenant's log and
proofs.auditrolls bundles up (achieved rungs, unmet reasons, assumption
and judge-waiver rates) and lists gaming signals: one piece of evidence
bound to many unrelated claims, replays that did not match, proofs that
restate the prose, and sources the run wrote itself.checkerssays which
checkers can run here, and why not.prune --older-than 90ddeletes old
bundles and keeps proof artifacts.show,auditandpruneread a
managed harness's bundles with--tenants-root.crewhaus init --provability <rung>starts a spec with the block and
the tool its rung needs (readandwebFetchatgrounded,evaluateat
checked), then prints anything still missing increwhaus lint's words.
init --interactiveasks one question about it, and its research template
starts atprovability: cited.- The
provabilitydefault skill tells the model what the gate checks and
how to cite so it passes: markers and definition lines, fetching before
citing, a computation cited astool:<toolUseId>, the proof steps at
proven, andProvabilityPreflightbefore finishing. The spec's own floor,
checkers and settings are filled in. It is registered only when the block is
on, so other bundles carry no new prompt text, and a gated parent's
sub-agents are shown the parts that apply to them; only the parent's final
answer is gated. - The research report gate. A research run's rendered report is checked
against the pages its citation tracker fetched. The report gains a
## Provabilitysection after## Citations,report.jsonaproof
field, and the bundle is saved as.crewhaus/research/<runId>/proof.json; a
provability_gradedline goes to stdout, andon_fail: haltexits 37. Its
evidence is fetched pages, so the gate reachesgroundedat most, and a
checkedorprovenfloor warns at compile time and when the run starts.
Branches are told how to cite, andCiteFactgives each fact the number it
will carry in the report. Without the block the report is byte-identical. - The managed gateway carries the bundle.
RunsCreateResultand
RunsContinueResultgain an optionalproof, so a client reads the verdict
beside thereply, which stays the answer alone, andruns.subscribe
streams theprovability_gradedevent. The protocol version stays
crewhaus.v1. - Measuring a rung.
crewhaus eval --provability <rung>runs every sample
through the gate at that floor without editing the spec, and adds the
provability.*graders:claimCoverage,rungAchieved,
citationValidity,assumptionRate,replayIntegrityand
costPerProvenClaim, which is reported and never a bar. A sample passes
only when your graders pass and every provability bar holds. A dataset
sample'smetadata.provabilitysets its own floor and the sources it
expects (expected_sources), each sample's bundle is saved as
<sample>/proof.json, and a custom grader reads it asRunArtifacts.proof.
A spec that declares the block is gated the same way without the flag; name
the graders withtype: registryin its graders file to grade it. A
--provabilityrun is never a baseline:--gate,--sentinel,--models
and--voiceare refused with it. A channel bot evaluated through
compile --with-eval-harnesshands its bundles to the graders too. - The optimizer, advice and watch-me read the gate. The optimizer may tune
provability.threshold,max_retries(not on research),
grounded.coverageandgrounded.window_tokens, and refuses any patch that
movesleveloron_fail, a whole-block patch included.crewhaus advise
flags a harness when, over five gated turns or more, at least a fifth end
failing (provability-pressure, naming the dominant unmet reason and
attaching amax_retriespatch where one may help, never a lower floor) or
stand unverified (provability-unverified). The watch-me report takes its
grounding numbers from the gate's verdicts for turns graded atgroundedor
above. - Checks.
crewhaus lintwarns about a floor the spec cannot reach (no
tool that can read a source atgrounded,provability-no-source-tool;
nothing onexecution.allowthe agent can call atchecked,
provability-no-execution-tool; anexecution.allowentry that names no
tool,provability-execution-unknown-tool), aboutformal.checkersthat
lists only sandbox checkers (provability-sandbox-only-checkers), a sandbox
checker that would wait for approval on a channel or managed harness
(provability-proofcheck-asks), and a waiver judge that is the serving
model (provability-self-judge).crewhaus compilewarns
provability-inert-block,provability-on-fail-degraded,
provability-execution-unresolved,provability-image-unused,
provability-research-rung-unreachable, andmodel-plan-self-judgeat
provability.judge.modelwhen the waiver judge is a serving model;
--strictfails on each.doctor --philosophy-alignmentgains three
findings (one citation-marker grammar in the tree, no model adapter in
@crewhaus/provability, the floor not optimizable), anddoctor --probe
probes the sandbox checkers the spec's harness runs.crewhaus sessions tailshows the gate's verdicts and the checker runs,crewhaus models explainandmodels auditlist the waiver judge, and Hangar serves a
run's bundles atGET /api/runs/:sessionId/proof. - New packages:
@crewhaus/claim-ledger(the text layer: claims,
citations, numbers, now shared byFactCrossCheck, the faithfulness graders
and the gate),@crewhaus/provability(the ladder, its verifiers and the
bundle),@crewhaus/proof-checker(the checker interface and the three
checkers),@crewhaus/tool-proof(the three tools),
@crewhaus/evaluation-service(one composition root for both in-loop gates,
which the cli, channel and managed emitters,crewhaus runand
crewhaus evalcall),@crewhaus/grader-provability,
@crewhaus/sandbox-image-z3and@crewhaus/sandbox-image-lean4. - For tool and library authors:
OperativeArggainsreadsand
readsWhen, for a path the tool only reads (the runtime-state floor leaves
it out; every rule still reads it);RuntimeBridge.protectedRootshands a
tool the run's state roots, and@crewhaus/tool-safety/fsthe checks a tool
that writes beneath a directory makes against them;@crewhaus/tool-fetch
exportsguardedHead, a HEAD request under the Fetch tool's guards (the
Fetch tool itself is unchanged);@crewhaus/report-writer/provabilityis
the research report gate; and@crewhaus/gateway-protocolexports the
bundle's zod schema,ProofBundle.
Tool contracts and settings
@crewhaus/tool-safety: shared guards for tool authors, so a tool
package does not write its own../regexruns a caller's regex under a
deadline and answers matched, not matched or undetermined;./streams
reads process output, HTTP bodies and files within a byte budget and
refuses FIFOs;./fsopens, writes, walks and copies without leaving a
root or writing through a link;./envreads a credential only from a
variable the operator allowed and scrubs what comes back. It runs on Bun
only; its README's adoption table says which helper replaces what.operativeArgson a tool definition: the input field or fields a
permission rule is about, and what kind each is — path, URL, command,
recipient, id or text. A declaration also says how a value is read: whether
it iswithinanother field (a repository asowner/repo, a path from the
tool'scwd, withwithinDefaultfor the folder a left-out one means),
what a left-out value stands for, whether the tool walks what is beneath a
path (beneath, withhiddenWhenfor a walk that skips hidden names and
beneathWhenfor one a switch turns on) or opens fixed files there
(opens), whether a command is a line a shell parses (shell, which Bash
and Shell declare) or is handed an environment or input (env,stdin),
whether a value is a secret reference (secretRef) or where the tool
copies from (source), the API a call names (service, with the tool's
newserviceDefaultssaying where a call that names none goes), whose own
copy it acts on (overlay), and a field the tool ignores when the call
names another (ignoredWhen). Every builtin that changes something,
reaches outside or walks a directory declares them, andbuildToolrefuses
a declaration that names a field the input does not have. An empty list
says no argument decides where the tool acts. Tool packages outside this
repo can checkTOOL_CONTRACT_VERSIONin@crewhaus/tool-catalog.ToolRegistry(@crewhaus/tool-capability): a harness can see the
builtin tools it was not given — search by text, narrow by category, or ask
for one tool's description, and each row says whether this harness runs it.
This is ergonomics, not a control: leaving a tool out oftools:is what
shapes a harness, and a harness that can write files can already edit its
own spec. MCP tools are known only once their server connects, so the
answer points atListToolsfor them.@crewhaus/tool-registry-manifest: every builtin tool's name,
description, flags, categories and operative arguments as data, generated
byscripts/gen-tool-registry.tsand checked for staleness by a test. It is
about 250 KB of description text in a module of about 500 KB, which is why
it is its own package andToolRegistryits own tool.crewhaus tools show <tool>says what a permission rule on it
checks: each argument a scoped rule reads, in words (a path read from
cwd, a repository matched asowner/repo, a Chat channel with the
origin of the API the call names, what a left-out value stands for, and
for Bash and Shell that an allow must match every command in the line),
and an example rule to copy intopermissions.rules, such as
ChatDelete(https://slack.com/C0123/*). Every example matches a call the
tool runs. It also says that an allow matches a recipient or an id only as
the call writes it:SmsSend(+1555*)asks on+1 (555) 010-0000, which a
deny on+1555*still stops.--jsoncarries the same answer.all-vector, a category holding VectorDelete alone.- New
tool_configkeys: the credential and destination lists above;
codeExecution.max_timeout_ms;imageGenerate.timeoutMsand
maxResponseBytes;chainread.allowed_originsand
federationDiscover.allowed_origins, which can only narrow;
token.metadata_origins;token.multicall3andchaincall.multicall3. - New compile warnings.
tool-config-unused,tool-unwired,
edge-unsafe-tool,sub-agent-tool-ungranted,tool-config-partial-cap,
model-plan-tool-config-unreachableandpermission-rulefail--strict.
provider-tool-cap,provider-tool-cap-unverified,
channel-plugins-at-start,model-plan-tool-config-widens,
model-plan-tool-config-narrowed,permission-rule-noteand
mcp-server-nameare informational. - Options: EvalBaselineCompare
allowDatasetMismatchand
minSharedFraction, ArchiveExtractmaxBytes, andplugins install --trust-anchor.
Changed
Before you upgrade
Changes that can affect a harness without provability:. The gate is off
until a spec turns it on, but these changes reach every harness that uses
what each one names.
continuity.proof: requireandoffare enforced. Since 0.3.0 both ran
the defaultladderand printed a boot note saying the mode was carried but
not enforced. The note is gone, and each mode now does what it says. Under
require, nothing completes belowproven. A plan stays open while any
step is only claimed, and thePlanUpdateandPlanCompletereplies name
the steps that still need proof.GoalWriteandGoalUpdaterefuse a goal
that would reach its target without being proven in the same call. A step or
goal proven by assertion under an earlieroffrun counts as unproven, in
the tools, the<current_plan>tail andhandoff.md, and the Thredz goal
mirror completes only a goal proven with verified evidence. A spec that set
requireand relied on the old fallback will now see these refusals. Under
off, meant for harnesses that keep no session log, the tool use IDs a
proof cites are recorded but not checked (one token each, at most 32 per
record),PlanComplete'saction_proofevent carries
verdict: "unverified"andGoalUpdate'sgoal_updateevent
verification: "unverified", and every view shows[proven (unverified)],
never[proven]; Hangar's Memory view shows it in amber. The
continuity.proofHonestygrader fails such a step (score 0) rather than
reading it as proven, so anoffharness graded with the continuity graders
sees that verdict move.ladder, the default, keeps its proof behaviour and
its tool descriptions. In every mode, the continuity skill gains a note on
requireandoff, and the/plancommand now explains
proven (unverified), so a harness with continuity sends slightly
different prompt text.- The agent's tools cannot write the run's own record. In every harness,
the gate andcontinuity.proofread what the runtime recorded as evidence,
which proves nothing if the agent can rewrite it. So the agent's tools are
kept out of the session logs (.crewhaus/sessions, or wherever
CREWHAUS_SESSION_DIRor the tenant puts them), the tool results the
runtime saved to a file because they were too long for the log
(.crewhaus/tool-results) and the proof bundles (.crewhaus/proofs). A
Write,Editor other call that is not read-only and names a path inside
one is denied, and so is a tool that acts on everything beneath a workspace
directory holding one, such asStateImportorStoreMigrategiven
.crewhaus. No permission rule can allow either; only
--permission-mode bypasslifts this. Reading from these directories is not
a write: a field a tool only reads from (CopyPath's andArchiveCreate's
source, an importer'sfile) is left out, so a copy, an archive or an
import of.crewhausor a session log runs as before, while a move of one
does not. The workspace root itself is left to the rules, soGitAdd .and
GitCommitrun as before.CopyPath,MovePath,RemovePath,
ArchiveExtract,GitApplyPatch,StateImportandStoreMigratealso
check where their own writes land and refuse one that would reach these
directories in every mode, bypass included: so removing or moving
.crewhausitself is refused, and so are a patch that names more files than
can be checked and aStateImportthat would replace a directory holding
one or write an entry into one.BashandRunCommandare not covered,
because the runtime cannot see where a shell command writes. Other paths
under.crewhausare unaffected. - A bundle with
evaluation:compiles to different code. This is the
release's one change to the shape of generated code. A cli, channel or
managed bundle used to carry its own copy of the in-loop judge; it now calls
wireEvaluationfrom the new@crewhaus/evaluation-service, the same code
crewhaus runuses, so the bundle's source changes and it imports that
package. The judge behaves as before. Recompile to pick it up. A bundle with
neitherevaluation:norprovability:compiles byte for byte as before. - Plan and goal titles are one line, and capped. In every
continuity.proofmode, the defaultladderincluded, a plan or goal title
and a goal unit are stored on one line: a newline becomes a space, so a
title the model writes can no longer print a forged[proven]step. A title
may be at most 512 characters and a unit at most 64.PlanUpdate,
GoalWriteandGoalUpdaterefuse a longer one, andGoalUpdaterefuses a
blank title, so the default mode can refuse input that 0.7.x accepted.
log_knowledge_gapshortens a topic too long for its goal's title, and says
so. WebFetchnames the page it returned. When a fetch is redirected, the
result'sURL:line now gives the address the page came from, and a new
Requested:line gives the one the model asked for. The model reads this
header, so its text changes after a redirect; a fetch that is not redirected
prints the same header as before.FactCrossCheckreads a lookalike character as the plain one. It now
compares a claim and its source through the gate's fold, in every harness
that grants it. A digit of any script (٣٠,३०), fullwidth and
mathematical letters and digits (30,𝟑𝟎), a superscript number where a
number would stand (³⁰ dollars), and a Cyrillic or Greek letter that looks
Latin inside a Latin word read as the plain letter or digit; a soft hyphen
or zero-width character inside a word is ignored; and a footnote mark on a
claim (…30 days.¹) is no longer a word the source must hold. So a claim
0.7.x reportednotFoundagainst such a source can now besupported, and
one it reportedsupportedagainst anothiding a zero-width character,
or spelt with a Cyrillic or fullwidth letter, is now apolarityConflict. A
word it reports missing is given in the plain spelling; the excerpt is still
the source's own text. Text with nothing outside ASCII is checked as before.all-safetygrants three more tools, and they run without asking. The
newproofcategory (ProofCheckers,ProofCheck,ProvabilityPreflight)
rolls up undersafety, so a spec that listsall-safetynow gives the
model these tools too;-all-proofleaves them out.ProofCheckersand
ProvabilityPreflightonly read, andProofCheckwith the builtin checker
starts no container and writes nothing but a new artifact under
.crewhaus/proofs, so all three are allowed by default:ProofCheckers,
ProvabilityPreflightandProofCheck(builtin)join the builtin
bookkeeping allow rules. A spec rule still outranks them, andProofCheck
withsmtorlean4keeps the mode's default and asks.compile --emit-as cf-workerwarns aboutevaluation:. The Cloudflare
Worker has never run the in-loop judge, and now says so with an
accepted-but-unwiredwarning. The edge compile also reports the spec and
lowering warnings a localcompilegives, such asmodel-plan-self-judge.
With--strictany of these fails the compile, so a--strictedge build
that passed before can now fail on one of them, a spec withevaluation:
included: fix what the warning names, and forevaluation:remove the
block or compile without--emit-as. The hosted compile returns the same
warnings beside the bundle and still succeeds.run_examsays which knowledge gaps it could not log. When the gap sink
fails (a Thredz outage or rate limit, say), the tool result used to count
each failure as a logged gap and name none of them. It now counts only the
gaps that landed and lists the ones that did not, one clipped line each and
at most 20 (… N more — every failed sample is in the artifacts), so a
failing sink cannot fill the model's context with the whole failure list.- New event kinds and values are additive. The session log and the trace
gainprovability_gradedandproof_checked, written only by the gate and
byProofCheck; the audit log gainsprovability_evaluated; under
continuity.proof: off,action_proofgainsverdict: "unverified"and
goal_updateaverificationfield; and the gateway's run results gain an
optionalprooffield (and their schemas now list thereplythey always
returned), with the protocol version unchanged. A reader written before
0.8.0 skips a kind it does not know, but one that switches on
action_proof's verdict meets a new value. crewhaus security digestlists two more audit kinds. The kinds it
reports as having no record now includeprovability_evaluated, which is
new, andfeedback_recorded, which the list had missed, so a digest of a
harness without the gate namesprovability_evaluatedthere.- SpecAdvise and Hangar show a little more. SpecAdvise's result gains a
proofBundlessummary in every harness that grants it, so the model reads
a few more lines from it, and Hangar no longer masks the value of
CREWHAUS_TENANTS_ROOTin what it serves, since it names a directory, as
CREWHAUS_SESSION_DIRdoes. crewhaus init --interactiveasks one more question. The scripted
questionnaire asks "How much proof should answers carry?" last, on the cli
and research shapes, and the conversational interview asks it too. A blank
answer, or the end of piped input, takes the default:offon cli and
citedon research, so a research harness scaffolded this way is gated at
cited.
The 0.7.1 hardening. Most 0.7.0 specs compile and run unchanged. These
changes can make one ask, deny, warn or fail --strict; each says what to
write instead.
-
Bun 1.3.14 or later.
crewhaus doctorchecks it. The Homebrew, Scoop,
winget and apt binaries bring their own Bun. -
List the variables a tool may read as a credential (the table under
Security). A call naming an unlisted HTTPauthprofile variable, notify
credential, code-hosttokenEnv, signing or pseudonym key, or EnvInspect
revealvariable is refused until it is listed. WebhookSign and
WebhookVerify needed no configuration on 0.7.0 and now need
allowed_signing_envs. ObjectPresign needs a profile:tool_config: secure: key_env_vars: [AUDIT_SIGNING_KEY] objectstore: credentials: r2: { access_key_id_env: R2_ACCESS_KEY_ID, secret_access_key_env: R2_SECRET_ACCESS_KEY }
-
List where SmsSend, PushNotify and WaitForPort may reach. SmsSend and
PushNotify reach onlytool_config.notify.allowed_sms_recipients(E.164
numbers, or a prefix such as+44*) andallowed_push_targets; an empty
list refuses everyone. WaitForPort probes any host but this machine only
when it is intool_config.proc.wait_for_port_hosts. -
DownloadFile and OpenExternal need a justification judge. Every call
now carries a justification, and outside the test runner the built-in rule
check denies it, as it denies every justification-gated call, whatever
NODE_ENVsays. What to set:- A cli spec:
security.justification.judge: claude.crewhaus runand
the compiled bundle both use it. It runs onclaude-haiku-4-5unless
security.justification.modelnames another model, and needs that
model's provider key (ANTHROPIC_API_KEYfor Claude) wherever the agent
runs; without it the agent stops at start. - Any other shape has no
security:block: set
CREWHAUS_ALLOW_RULE_BASED_JUSTIFICATION=1where it runs to accept the
rule check. crewhaus evalandcrewhaus optimizedo not use the judge yet: set
the same variable for them.
OpenExternal also asks in auto mode unless a rule allows it.
- A cli spec:
-
ImageGenerate pointed at another endpoint needs
OPENAI_BASE_URLset to
the same origin astool_config.imageGenerate.openaiBaseUrl, or it stops at
start. Plain http is refused except on loopback. -
Some permission rules need rewriting:
- Use the tool's name, not its spec key:
RemovePath(tmp/**), not
removePath(tmp/**). - An argument-scoped allow must cover every argument it is about.
Grep(src/**)no longer allows a search just because its path is under
src/, since the pattern counts too. Keep a bareGrepallow and deny
what must stay out. - A value the call leaves out that stands for everything needs an allow that
covers everything:SearchIssues(**), notSearchIssues(*), for a search
that names no repository (also SearchCode and DeployInspect);
KvList(ns/**), notKvList(ns/*);Glob(src/**)for the pattern
src/**;EvmGetLogs(1/*)for every contract's logs. - An allow naming a command covers it in the workspace root. RunCommand,
RunPipeline, Retry, ProcessStart, RunBuild, RunTests, Format and
HooksManage ask when the same command runs in a subdirectory or sets
envSet.RunCommand(**)still covers every call. - A Bash or Shell allow must match every command in the line.
Bash(git *)no longer allowsgit pull && npm testor
git log | head; they ask. Write one rule that covers each command, or
write the rule as the same chain:Bash(cd ** && make *)allows
cd build && make allbut notcd build && rm -rf ~. A line with
$(…),$((…)), backticks, a$'…'string, a here-document or afor
loop asks unless a bareBash,Bash(*)orBash(**)allows it; those
three allow what they did before. - An allow on a program or script names where it really is. If
tools
links tosafe/,RunCommand(./safe/run.sh)grants./tools/run.shand
RunCommand(./tools/**)asks. A program or script that a..or a link
takes out of the allow's folder asks,/opt/tools/../evil.shunder
RunCommand(/opt/tools/**)included. A node orbun runscript reached
through a link and then a..asks unless the allow covers both places
it can read as, and a command with two such words is granted only by
RunCommand(**). A program reached through a link to the file itself
(./node_modules/.bin/eslint) is still allowed by its link name. - A Bash allow must also cover where each program really is:
Bash(sh **)still grants a script reached through a linked package
folder, butBash(./scripts/**)asks on./scripts/lnk/run.shwhen
lnkleads out ofscripts/. A line in which a shell reads its commands
from a pipe (curl … | sh), or a program is written under${PWD%/*}or
$OLDPWD, sets off every deny and ask, and asks unless an allow covers
every command (BashorBash(**)). - A path a tool reads relative to
cwd(git and formatterpaths) that is
absolute or has a..asks under a scoped allow, and Lint, Format and
RunTests refuse it. Setcwdto the folder you mean and list paths below
it. - A deny or ask on a file now also stops a tool that walks or dumps the
folder holding it, the workspace root included, unless the file is
hidden and the tool skips hidden names. UnderalwaysDeny *(**/.env)
the ordinary StateExport, NoteSearch, EventQuery, TraceQuery,
SessionSummarize, CostSummarize, AuditVerify, PermissionsSuggest,
CiWorkflowAudit, DiskUsage and ChecksumVerify calls are denied; under
alwaysAsk *(.crewhaus/**)all of them but CiWorkflowAudit ask, which
parks a headless run. Tree, Grep, FindFiles and the other walkers that
skip hidden names run as before. To keep such a rule on file readers
only, name them (Read(**/.env)); to keep a scan you rely on, point it
at a folder (TodoScanwithcwd: src). An allow on a walked folder
still reads the folder alone. - A secret rule reads the file a reference opens.
SecretLookup(file:public/**)no longer allows a link that leads
elsewhere, and a deny or ask on.envor.env.localnow stops a
SecretLookup, EnvFileUpsert or SecretRotate call that names a secret as
API_KEYorenv:API_KEY. An allow that names the secret as the call
writes it still grants it:SecretLookup(API_KEY)forAPI_KEY,
SecretLookup(env:API_KEY)forenv:API_KEY. - In a spec that reaches more than one code host,
IssueCreate(acme/app)
covers only the APItool_configis set up for. Name the host:
IssueCreate(https://ghe.example.com/acme/*), or
IssueCreate(https://*/acme/app)for that repository on any https host.
A spec with one host keeps its rules. - A scoped allow on RateLimitStatus names the API it reads,
RateLimitStatus(https://api.github.com/**), orRateLimitStatus(**)for
any.RateLimitStatus(*), or one written on another value of the call
such asRateLimitStatus(github), now asks. - An API-mode Chat rule names the API:
ChatPost(https://slack.com/C0123),
orChatPost(https://*/C0123)on any API;ChatPost(C0123)alone now
asks. A post through an incoming webhook is allowed by the variable that
names it,ChatPost(SLACK_OPS_WEBHOOK), not by a channel, and
WebhookPost by itsurlEnv,WebhookPost(DEPLOY_HOOK_URL). - A deploy call that names a
tenantis read as<tenant>/<name>/<env>:
DeployRollback(billing/staging)no longer covers a tenant's copy of
that pin. WriteDeployRollback(acme/billing/staging). - A TempDir allow names the directory it makes:
TempDir(.tmp/*)for the
ones it makes by default, notTempDir(scratch). - A pattern aimed at something that is not where the tool acts, such as a
message body, no longer grants a call, and a deny or ask on it fires only
when every text value of the call matches, as in 0.7.0. To stop a call,
deny the tool or its destination. - A rule on a chain tool is written
Tool(<chainId>/<address>).
- Use the tool's name, not its spec key:
-
Approvals parked before the upgrade settle as before when the call's
path goes through no link. Through a link, the call is asked once more. A
refusal still applies. -
SpecPin, DeployRollback and DeployInspect take a spec name as the
registry stores it. A name the registry would store under another
spelling (Brewbird SupportisBrewbird-Support,.billingis
billing) is refused with the name to use; DeployInspect lists them. -
A mirror or chain node that redirects now fails. Point
DependencyAudit'sendpoint, or a chain'srpcUrls, at the address that
answers directly. -
MCP tools are named
mcp__<server>__<tool>, as the docs always said;
they were registered as<server>__<tool>. Rules, model-profile lists,
skill and sub-agenttools:, hook matchers,rate_limitsand eval
expectations written the old way keep working, and an approval parked
before the upgrade still settles. Update instructions that name a tool the
old way, and hook scripts that compare the tool name: compare the new
legacyNamefield, or accept both.mcp__<server>__*in a model profile's
tools:now selects that server's tools, where before it selected none. -
mcp_serverskeys must fit in a tool name: letters, digits,-and
_. A key with any other character fails compile with a suggested rename.
A key with__, or_at either end, still works but warns, since two
servers' tool names could collide. An MCP tool whose full name would be
longer than providers accept is left out with a warning; the server's other
tools still register. -
tool_configis now applied, so it is now checked. Two different
blocks for one package are an error. A key no listed tool reads is
tool-config-unused(an error under--strict); for atool_config.mcp
block, which never did anything, the warning says where itsdestructive
andrequireJustificationflags go:
mcp_servers.<server>.tool_flags.per_tool.<tool>. An allow-list entry that
is not an origin, or a key the tool refuses, is reported at compile, with
--emit-as cf-workertoo, where it used to emit a worker that failed to
load. Anything else a tool refuses stops the harness at start. A value written
$UPPER_SNAKEis read from the environment: set the variable, or a spec
that meant the text literally stops at start. A credential written
${API_KEY}or$api_keyis refused (write$API_KEY), and cf-worker
refuses every$VAR.tool_config.codeExecutionconfigures only Python,
JavaScript and Shell; give Fetch, WebFetch and ImageGenerate their own
blocks. -
A tool list longer than every model in the spec can take fails
compile. Azure OpenAI and Groq accept 128 tools per request, and Gemini
(on Vertex AI too) 512;tools: [all-code]is over 128. Narrow it with
smallerall-<category>roll-ups or-toolexclusions. For anopenai/
model it is a note, sinceOPENAI_BASE_URLmay point at a server that
takes more, and the run stops at start, before any model call, if the list
goes to api.openai.com. When only a fallback, tier or pool model is over
its limit, compile notes it and a pool never routes a turn to that model.
Neither note fails--strict. On 0.7.0 every call of such a spec failed. -
compile --emit-as cf-workerrefuses a builtin the spec's own shape
refuses, with the same messagecompilegives:sendMessageon a cli,
workflow or graph spec (only the channel shape carries it; the worker
compiled it, and every call failed), and theevm*tools on a cli spec.
Remove them fromtools:. -
evmSendTransactionwarnstool-unwired(an error under--strict):
no custody provider that can sign ships in this release, so every call
failed. Remove it;evmSimulateruns the same transaction without signing. -
New warnings that fail
--strict:- A permission rule that can never fire (see CLI under Fixed): rewrite it as
the warning suggests. - A deny or ask below an allow that always matches first: move it above
the allow. sub-agent-tool-ungranted: a sub-agent lists a builtin its parent never
registers, so the sub-agent never had it. Add the tool to the parent, or
remove it from the sub-agent.edge-unsafe-tool:compile --emit-as cf-workernames each builtin the
edge leaves out. Remove it, or compile for Bun.accepted-but-unwiredfortools:andtool_config:on onchain and
onchain-game, and fortool_config:on voice: none of them is wired yet.
Remove them.crewhaus lintreports these too.
- A permission rule that can never fire (see CLI under Fixed): rewrite it as
-
A
tools:entry named after a JavaScript built-in (constructor,
all-toString) is refused as unknown. It compiled to a bundle with no tools
or a broken import. -
all-state,all-memoryandall-data-storesno longer grant
VectorDelete, which deletes from a remote store. Grant it with
all-vectorortools: [vectorDelete];all-networkdoes not include it.
A-vectorDeleteexclusion after those roll-ups still compiles. -
A time without a UTC offset is refused by QuietHours, RateLimitGate,
EmailCompose, EmailSend, EmailSendPreflight, ApprovalStatus and
ApprovalsInbox, whose schemas always asked for one, and in a plugin's
notAfter. Write2026-09-17T23:30:00Zor2026-09-17T23:30:00+01:00.
CronList reads an offset-lessnowin thetimeZoneyou give, UTC by
default, and HarnessJobStatus reads an offset-lesssinceas UTC. -
A bundle that reads a chain needs its RPC variables at start. A graph,
workflow or crew bundle that lists an EVM tool stops at start when a
$VARin thechainsblock is unset. Set it where the bundle runs. -
Git tools need the harness to own its repository. Run the harness from
the repository's top level, or give it its own repository. -
Some edits are for people, not tools. SpecPatchApply refuses prompts,
tool grants, models, human gates,transaction_policy,chainsand
security, at any depth. EnvFileUpsert and SecretRotate refuse a value a
shell or Bun's.envloader would expand ($, backticks,;,&). Edit
those by hand, or withcrewhaus optimize. -
PackageInstall never removes a package. An apt install that would
remove one is refused with the command to run yourself, and a name apt reads
as a pattern is refused. -
Plugins.
plugins installneeds a trust key (--allow-unsignedis for
development). A signed plugin must carryentrypointDigestand be one
bundled ES module. A plugin tool named likeserver__toolis left out,
because rules written for MCP tools would match it: use single
underscores. -
CREWHAUS_SANDBOX=noopturns code execution off. Tests that set it to
run code in-process should call
registerCodeExecutionConfig({ backend: "noop" })or pass a sandbox. -
Eval harnesses run your spec's allowed tools for real, with their side
effects, and need every$VARyourtool_configreads.
Other behaviour changes
- A compiled cli bundle whose spec declares
security.justification
writes an audit log to.crewhaus/auditin its working directory, as
crewhaus rundoes: every justification verdict, and every outbound call
the egress check warned about or blocked, hash chained.
CREWHAUS_SECURITY_AUDIT=0turns it off. One that cannot create the log (a
read-only working directory) says so in one line and runs without it. A
bundle whose spec declares no judge is unchanged. - Plan mode honours deny and ask rules. It decided on the tool's
read-only flag alone, so analwaysDenyon a read-only network tool did
nothing there. A matching deny or ask now denies; allow rules are still
ignored, so plan mode can only get stricter. - Deny and ask rules fire in more places — on defaults the call leaves
out, on directories above a denied one and the folders a tool walks, on
every spelling of a destination, on where a program, script or secret file
really is, on what a tool copies from, on a command reached through its
directory, environment or input, on any command in a Bash line, and on the
API a call names. A deny that fired in 0.7.0 still fires. Allows got wider
only toward the same place: an allow on a path or URL meets more spellings
of what it names (an absolute path,./, a symlinked folder, another
letter case where the filesystem ignores case, a URL's default port, its
scheme or host in another case), an allow naming where a program really is
grants a call that reaches it through a symlinked folder, and a scoped Bash
allow also matches a line that differs from it only by blanks around it or
a trailing comment (git status # checkunderBash(git status)). - A call whose input the tool would reject is refused before any rule or
approval. It could not run anyway. It is not counted as a denial in eval
safety violations or deny alerts. - A
CREWHAUS_SANDBOXvalue that names no backend blocks code execution,
with a message saying what to set. - Tool calls in one turn run in the order the model wrote them, with or
withoutagent.streaming, so a read after a write sees the write. - A long one-line tool result reaches the model as a preview, like a
multi-line one; the full output is still saved. - Read and Edit refuse files over 64 MiB (ReadLines reads part of one),
and ReadLines keeps to a character budget (maxChars); a cut line is
reported with its full length. - Code execution keeps the start and end of long output with a line
saying how much was dropped, and reports a cancelled run as cancelled.
tool_config.codeExecution.max_timeout_mscaps the timeout the model may
ask for; unset, it may still ask for up to ten minutes. In a pool candidate,
a cap written underpython,javascriptorshellcaps only that tool
(tool-config-partial-cap); write it undercodeExecutionto cap all
three. - ImageGenerate stops when the turn is cancelled, after three minutes
(timeoutMs) or past its response cap (maxResponseBytes), and refuses a
base URL with a query. Its description now says each call is billed. - Git: a wildcard in a path is no longer expanded; GitLog and GitShow no
longer verify signatures; changes inside a submodule's working tree are not
reported from the superproject; a binary a repository's textconv rendered
as text is reported as binary. - Code-host search: on GitHub, a
repo:,org:oruser:qualifier in
the query must match theownerandrepofields (text in quotes is
searched as written); on GitLab, SearchIssues with an owner but no repo is
refused. - ChecksumVerify walks dotfiles, links and hidden folders. A call that
names neither a directory norexcludeleaves the root's.gitand
node_modulesout and lists them as excluded. A walk that would pass its
limit is refused before hashing. - ArchiveExtract refuses an archive that declares more than
maxBytes,
which defaults to 1 GiB and can be raised to 16 GiB, or holds a FIFO,
device or socket. - New limits refuse rather than slow down: SchemaDiff past 256 levels of
nesting, RobotsCheck past a path and query longer than a web server
accepts, and Slugify and GlossaryReplace when a replacement would grow the
text past its limit or take too many passes over it. Slugify and
GlossaryReplace say which limit they hit and never return a cut text.
SchemaDiff lists the first changes when there are too many to show, with
how many it left out; its verdict and counts still cover every change. - ImportCsv and ImportJson no longer fail on an extremely long number.
It is imported as text, and ImportCsv infers its column as text; in a
column that is alreadyINTEGER, its row is rejected as not an integer. - Chain and DeFi reads: EVM reads time out after 30 seconds and refuse
oversized answers; DeFitimeoutMsbounds the whole call, and
PortfolioValuation's default is a 60-second deadline for the call. - Budgets and pools: FindElement's grounding calls count toward
budget.usd, and a pool candidate whose model cannot see images is not
offered Screenshot. - A knowledge source whose URL carries a credential gets a new document
id. Re-index a persistent vector store to drop the old chunks. - SpecPin and DeployRollback refuse spec names that start with
_,
which the registry's listing hides. - Typecheck's first run after upgrading is cold, since its cache moved
out of the project. - Discovery tools strip hidden Unicode (tag characters, stray variation
selectors, soft hyphens) from authored fields and say so. - For library users, many result shapes gain fields (
undetermined,
outputIncomplete,truncatedBy,refused,determined,
anchorsUncheckedand others),Sandbox.execreportsabortedand byte
counts,@crewhaus/preflight'sBundleFreshness.stategains
"unreadable", and a parked approval records theplacesit was granted
for.
Fixed
Found while building provability
- A session log that cannot be read to its end no longer breaks
PlanComplete. A torn or malformed line, which a crash mid-append leaves,
or a link where the log should be, madePlanCompletefail with a raw error
and noaction_proofrecord. It now reads the log up to the bad line:
evidence found before it proves the step as usual, and evidence the line cut
off ismissing, recorded like any other rejected proof, so the step stays
open. Thecheckedrung reads the same log the same way. - The optimizer can no longer remove a seeded
model_pool.learningblock.
A routed eval pinsmodel_pool.learning.seed, and a patch that changed the
seed was refused, but aremoveof the whole block went through. It is
refused now when the spec sets a seed. A patch to a leaf no target may tune
says why, instead of suggesting it be added toOPTIMIZABLE_PATHS. - A spec patch that adds a key under a scalar (
continuity: true,
provability: cited) fails as aSpecPatchErrorthat names the scalar to
expand, instead of a bare YAML error. - The Hangar spec editor, preflight and the hosted compiler name the key
that is wrong inside a block that takes either a value or an object.
continuity: { plan: 5 }reports
continuity.plan: Expected boolean, received number, where 0.7.0 reported
continuity: Expected boolean, received object.crewhaus lintand
compilestill report that block ascontinuity: Invalid input, and name
each wrong key only insideprovability:. - The gateway's result schemas list
reply. The managed daemon has always
returned the run's final text asreply, but the strictRunsCreateResult
andRunsContinueResultschemas in@crewhaus/gateway-protocolleft it
out, so no real result validated against them. - The runtime smoke job runs on Ubuntu 24.04.
ubuntu-latestmoved to
26.04 on 2026-10-20, where the pinned Playwright cannot install Chromium,
and the release workflow reuses this job as its publish gate, so no tag
could ship until the runner was pinned.
Every shape runs the 0.7.0 tools
- Graph nodes, workflow steps, crew roles and the channel, managed,
research, batch and browser shapes can use the 0.7.0 tools and
categories.tools: [jsonQuery]ortools: [all-data]compiled only on
the cli shape and failed elsewhere withunknown tool. Every shape now
reads one builtin table. cf-worker runs its edge-safe set and says which
tools it leaves out (edge-unsafe-tool). Voice, onchain and onchain-game
accepttools:andtool_config:but do not wire them yet, and say so
(accepted-but-unwired, an error under--strict). crewhaus evalandcrewhaus optimizerun specs that use the new
tools, with the tools andtool_configthe compiled bundle uses, instead
of stopping with a stack trace. A bridged eval of a channel spec with
plugins:says it runs without the plugins' tools.- Eval harnesses built with
compile --with-eval-harnessapply the spec's
tool_configand permission rules, so the agent you grade is the agent
you ship. - Code execution works on every shape that registers it. The managed
daemon denied every call and pointed atCREWHAUS_SANDBOX, which it never
read. Sub-agents given Python, JavaScript or Shell now run them when the
parent has a sandbox. - Sub-agents get the tools their
tools:list names, categories and spec
keys included; before, those gave the child no tools at all. plugins:on a channel daemon is loaded. It was accepted and did
nothing. A plugin the daemon cannot load is skipped with a warning at every
start.- A compiled cli bundle starts when a plugin adds a tool named like a
builtin: the plugin's tool is skipped with a warning. - A compiled cli bundle uses the justification judge its spec names.
Onlycrewhaus runreadsecurity.justification.judge, so a bundle
checked every justification-gated call (HttpRequest, EmailSend,
DownloadFile and the rest) with the built-in rule check, which denies them
all outside tests. The bundle now builds the same judgecrewhaus run
does and writes the same audit log. Recompile to pick it up. A sub-agent
now judges its gated calls with that judge too, in the bundle and in
crewhaus run, and logs them on the same audit log. - A model profile or pool candidate can narrow to a tool the shape grants
through a category:tools: [csvParse]under a shape's
tools: [all-data]was refused as "not one of the shape's tools". - Cloudflare Worker bundles install again: their
package.jsonpins the
release that built them.
tool_config reaches the tools that read it
- Each package's block reaches its tools on every shape, in
crewhaus runand increwhaus eval, and a model-pool candidate reads the
same keys. The http, codehost, notify, obs and defi tools ignored the blocks
their READMEs document, so their network tools refused every request.
Write the block under the package key (http), a tool's key
(httpRequest) or its registered name (HttpRequest);crewhaus tools show <tool>names it. tool_config.WebFetchrestricts WebFetch: written with the registered
name, the block was dropped at boot and WebFetch could reach any host.
tool_config.fetchreaches DependencyAudit's OSV mirror list without Fetch
intools:, andcrewhaus runand the browser shape apply
tool_config.imageGenerate.- Tools that read a chain work when the spec declares one. The chaincall
tools,erc20Balance,erc721TokenInfo, TokenResolve's on-chain check,
and on graph, workflow and crew theevm*readers andevmSimulate, take
their RPC from the spec'schainsblock. Without one they refuse and name
the block to write, and compile warns (tool-unwired). A graph, workflow
or crew bundle that lists an EVM tool reads thechainsblock's$VARs
when it starts, and stops if one is unset; on 0.7.0 it started, and those
tools failed on every call. - VectorDelete deletes from the store
tool_config.vectorDeletenames,
anderc721TokenInforeads metadata from the origins
tool_config.token.metadata_originsallows. Both refused every call. - The bundle README tells the truth about each tool: its scope and
justification gate come from the tool, "configured by" appears only when
the block reaches it, a tool the bundle does not carry is marked "not
wired", and the$VARnames a block reads are listed.
Model providers
- Every builtin tool description fits OpenAI's limit, so granting any
tool or category no longer breaks every request on OpenAI or Azure OpenAI. - On OpenAI, a tool with an optional argument is not sent in strict
mode, which forced the model to sendnullfor arguments it meant to
leave out. - A deeply shared
$refschema from an MCP server no longer grows to
megabytes on Gemini, Bedrock or OpenAI requests, and Gemini schemas no
longer carry the numeric enums Gemini rejects. - Every tool call has its own id on Gemini and on OpenAI-compatible
servers that send none, so a long result's "full output at …" pointer names
that call's own file. - Claude Opus 5.5 and Sonnet 5 are priced and sized correctly. They were
billed at older models' rates, and Opus 5, Opus 5.5 and Sonnet 5 were
treated as having a smaller context and output than they do. crewhausno longer runsclaude --versionon every command, or
warns that it could not, unless it uses a Claude subscription token.
Answers that say when they do not know
A result the tool could not determine used to read as a definite one. It now
says "unknown", null, "undetermined" or "refused", with the reason.
- VatIdValidate answers "could not check" for UK numbers instead of "not
registered": HMRC's old free checker is gone. - CronList returns
ok: falsewhen it could not read a scheduler, instead of
"nothing scheduled", and a newdeterminedfield says whether anything was
read. - PackageQuery never accepts a winget source agreement for you: an
outstanding one makes the answer "unknown", with the command to run once. - ExifRead reads the whole file for GPS, and
hasGpsisnullwhen it
cannot tell. - JsonSchemaValidate answers
valid: nullfor a schema too expensive to
finish, and ValidateRecords counts such rows as undetermined. - UrlReachable, LinkCheck and HealthProbe report a URL the allow-list refused
as refused, not down, and LinkCheck marks URLs the deadline never reached as
skipped. - ChecksumVerify checks every entry, dotfiles and links included, and a walk
that stopped at its limit or an unreadable entry is notok. GoldenCompare
calls an unreadable golden unreadable, not missing. - Browser Click, Type, Key, Scroll and FindElement record a driver or
grounding failure as a failed call, and withstreaming: truea failed
action cancels the ones queued behind it, so typing no longer goes into a
field the click missed. - ClipboardRead, the Windows printer and window-focus probes, TlsInspect,
the preflight bundle check and the audit verifier say "unknown",
"unreadable" or "could not verify" where they reported empty, missing or
tampered. - The HTTP, notify, code-host and observability tools report a timeout only
when the timeout caused the failure. - EvalBaselineCompare fails runs that share no samples or name different
datasets; passallowDatasetMismatchto compare across datasets on purpose. - PortfolioValuation lists the holdings it did not price, with the reason, and
LedgerReconcile lists the statement rows it could not read. - Reusing an
idempotencyKeyfor a different message sends nothing and says
so, instead of reporting the earlier message as sent. A Slack verdict cut
short is "unknown", not "sent". - GitApplyPatch no longer reports "applied" for a patch git partly skipped,
and a three-way apply that leaves conflicts says so. - Grep says "no matches in the lines searched" and names the lines and files
it could not search. - FactCrossCheck reports a claim it could not check in full as unchecked,
with the reason, instead of checking it in part: one that quotes more than
16 different spans (split it into smaller claims), or one the call has no
reading budget left for (check it in a separate call). - MarkdownLinkCheck reads a link target's headings only in the documents it
was asked to check, and lists anchors into any other file under
anchorsUnchecked. - ContractInspect tells a probe that reverted from one the node could not
answer, so a contract whose probes revert is no longer refused on a chain
without Multicall3. EvmSimulateBundle falls back toeth_call, and
GasMarketRead answers without fee percentiles, when every node says it
lacks the method. A chain read that fails on every node ends with the last
node's reason.
Tool results
- A hard-linked file gets the same answer every time on macOS. A file
with a second name can be reported under either name there. So the
observability tools sometimes refused a workspace file because its other
name was outside, and an append sometimes refused it with the wrong reason.
Both cases refused; now the decision rests on the name you gave. - WaitForOutput sees the line it waits for. It reported "process
finished without matching" for a line a short-lived process printed just
before it exited. A process now counts as finished once it has exited and
the output being watched has closed, so a process it started that still
holds that output open keeps the wait going until the line or the
deadline. A wait on a process you stopped ends once that process exits, so
lines it prints while shutting down are still seen. - Money is exact. Amounts are whole minor units, and totals are exact or
refused. RuleScore adds exactly (0.7 + 0.1 meets a band at 0.8),
CostBasisCompute counts exact decimals, StatementParse reads amounts exactly
and keeps OFX transactions whose closing tag is missing, and
OraclePriceRead reads a Pyth confidence correctly. - Decoders refuse what they cannot represent. AbiDecode refuses a value
its type cannot hold; AbiEncodeCall and TypedDataHash check an address's
EIP-55 checksum; TypedDataHash refuses a message missing a field and an
odd-lengthbytes. - Edits land as written. Edit writes
$&,$$and the like exactly —
the file could differ from the diff it showed — and Slugify, InvoiceRender
and the prompt optimizer insert them literally. Write, Edit,
FrontmatterWrite and NotebookEdit keep a file's permissions, and Read and
Edit keep a UTF-8 byte-order mark. - Dates read the same on every machine. HttpRequest and ErrorClassify read
a Retry-After date as GMT, and the tools that took a time without a UTC
offset no longer read it in the host's own zone (see Changed). - Also fixed:
- JsonQuery no longer drops matches in large arrays, and TableJoin's
maxRowsbounds the work as well as the output. - JsonSchemaValidate follows recursive schemas through
anyOf,oneOf,
not,ifandcontains. - CSV tools refuse a delimiter they could not read back.
- SemverResolve and the registry tools read
1.2,<=1.2,~1and hyphen
ranges the way npm does, and order prereleases correctly. - WorkspacePackages reads
?and!globs correctly. - CommandExists finds a Windows program by its bare name.
- Discord calls use the
Bottoken scheme. - Typecheck and Diagnostics keep the checkers' caches out of your project.
- DnsLookup keeps to one budget for the whole lookup.
- WatchPath reports a rewrite or permission change made just after it
starts. - GlCodeSuggest codes large batches in full.
- ExifStrip also removes metadata between scans and anything appended after
the image, such as previews and motion-photo video. - The wiki tools check an article's title, tags and sources for prompt
injection, not only its body. Ordinary API phrases ("override the
Content-Type header") and a tool's own refusals are no longer flagged as
injections. - SpecDiff flags a changed MCP server command or endpoint, and a new env or
header key, as widening. - Chain RPC fallback no longer lets a stalled first URL use up the deadline.
- Marking a plan step or goal proven pins its evidence before recording the
status. - Plugin tools written with zod 4 show their parameters to the model.
- SpendLimitCheck says it computes a verdict from its inputs and enforces
nothing itself, and ExportCsv says its cells are written verbatim,
formulas included. - SeoLint keeps every letter of a link's text in Arabic presentation forms
and CJK compatibility characters when it checks for vague text such as
"here". - GlossaryReplace matches letters the same way with whole words on as off
when case is ignored: the Kelvin sign, the long s and the capital sharp s
no longer match another letter only with whole words on. - A registry manifest that lists a version name too long to be a file no
longer blocks DeployRollback and the other registry tools. - Most tools that refuse a path for its length say so, instead of calling
it outside the workspace; the core file tools do not yet.
- JsonQuery no longer drops matches in large arrays, and TableJoin's
CLI
crewhaus lintandcompilepoint out permission rules that can never
fire, in every rule list —permissions.rules, model profiles and
sub-agents: the spec key where the tool name belongs (removePath(tmp/**)
forRemovePath(tmp/**)), a URL with a method in front, a near-miss or
re-cased name, a key-form glob (codegraph*), an MCP server the spec does
not declare, and an argument pattern on a tool that has no such argument.
compile --strictfails on them. A rule naming a tool nothing knows (a
plugin may register it) gets a note, never a fix, and an allow is never
"corrected" into a tool that can change or delete things.crewhaus lintandcompilesay when a deny or ask can never fire
because an allow above it always matches first: the first matching rule
decides, soalwaysAllow BashabovealwaysDeny Bash(rm -rf **)lets every
rmrun. The warning names both rules; move the deny above the allow.
compile --strictfails on it. A sub-agent'sallowlist is read before
itsdenylist, so there, narrow the allow. Only a case that can be proven
is reported. In a spec whose mode isplan, which reads no allow, the deny
does fire: it is a note that it would stop in another mode, and--strict
does not fail on it.crewhaus compile --helplists every warning that never fails
--strict. It is now built from the same list--strictreads; the
hand-written copy had left outmcp-server-nameandpermission-rule-note.- A bundle built with the optional IR passes decides as
crewhaus compile's
does. The passes (applyIrPasses, which the compiler worker's
POST /compileaccepts) re-sortedpermissions.rulesdeny-first, so an
allow written above a narrower deny was a deny there. Rules keep their
order; only an exact repeat is dropped. Such a bundle now lets that allow
decide, so the compile returns the warningcrewhaus compileprints for
it: move the deny above the allow. crewhaus lintagrees withcompileabout tools, and knows the tools
athredz:block adds. It also reports a key the spec's shape accepts but
does not wire (accepted-but-unwired), which it used to call clean.
lint --fixedits only the fields it fixes, insidetools:lists compile
reads, never prompt text or a$profilereference, and says when it
skipped a file that is not valid YAML.crewhaus permissions suggestproposes rules scoped to what was
approved —RemovePath(build/cache),RunCommand(git status)— instead
of a blanket grant. A proposal that can only cover the whole tool is marked
BLANKET GRANTwith the reason, and any deny or ask in your spec or the
builtin floor that a proposed allow would override is named, and each
proposal is checked to allow the calls it came from. The
PermissionsSuggestandApprovalStatustools do the same, and approval
rows show the record a call acts on.crewhaus toolsreads the tools a spec really grants.suggestreads
the instructions of every step, node and role and no longer proposes a tool
the spec's shape cannot run;auditnever tells you to drop an exclusion.
showsays which shapes run a tool and whichtool_configkey configures
it, takes the registered name in any case, and suggests the tool a typo
meant.listmarks justification-gated tools, andcategoriessays what
all-networkleaves out.- Tool errors name the problem. A builtin the shape cannot run is refused
by name, with the shapes that carry it;unknown toolsuggests the nearest
builtin; errors start with the spec path they concern (nodes.plan.tools:). - A category exclusion only has to remove something you included, so
tools: [all-code, -all-network]compiles. compile --strictscope-audits theevm*andsendMessagebuiltins
instead of skipping them.- PermissionAudit reports each builtin's real flags and checks every rule
list. An unruled call gets the decision the engine would make, rules that
can never fire are listed, and rules on Thredz, MCP and runtime-added tools
get their own sections instead of being called dead. ToolInventory checks
builtin names against the spec's own shape without being asked. tools audit,permissions suggest,doctorand PII tuning no longer
count the approvals log as a session.- Hangar reports a refused baseline re-pin with the reason, not "internal
error".
Packaging
- The
crewhauspackage declares everything it loads at startup, so an
install that lays packages out differently no longer fails with "cannot
find package". - Published packages carry LICENSE, NOTICE and a README, declare the Bun
they need, resolve withrequire()on Bun, and no longer include test
fixtures. Container images are built on Bun 1.3. - A release never publishes a package whose dependency failed to
publish, and the npm ownership check fails when the registry cannot be
reached instead of passing.
Security
The evidence a run is checked against
- The agent cannot rewrite the record its answers are checked against. The
runtime-state floor under Before you upgrade holds in every harness: no
rule lets the agent's tools write the session logs, the spilled tool results
or the proof bundles, and the tools that write beneath a directory check
their own targets. And a plan or goal title can no longer carry a forged
[proven]step into a plan file, in anycontinuity.proofmode. - Evidence the model controls does not count. The gate reads none of these
as a source: text a runtime tool or a sub-agent hands back (Task,
Consult,Recall,Skill; a sub-agent's evidence is the fetches its own
log holds); an echo of the call's own input from an MCP or plugin tool, or a
row an MCP store kept from text an earlier call sent; a response outside
2xx, such as an error page; aurlorpathan MCP tool's declared schema
does not list; anything under.crewhaus/or.crewhaus-shared/, through a
link or through a copy, move, concatenation or archive of it; the
[user prompt: …]lineWebFetchputs before a page; a page served after a
redirect to a denied or private host; and a URL that carries a user name or
password. Aproof:citation needs theProofCheckcall that wrote the
artifact in the run's evidence, and an artifact edited after the check is
proof_missing. sources.resolve: headis a guarded egress. A cited URL's HEAD request
goes through tool-fetch's guard, with the private-address check and the
spec'sdenylist on every redirect hop, and its answer is kept for the
session. The egress classifier screens the URL first, so one carrying text a
tool read is never sent unless a tool that reads the network returned that
very URL. One verification sends at most 50, six at a time, within 15
seconds and the turn's deadline; a URL past either isunresolved. Each
request is anegress_decisionaudit record that names the host, never the
path or query.- Proof text runs only where it cannot reach anything. The builtin checker
parses a certificate and evaluates it; it never runs code. Z3 and Lean 4 run
only in a container sandbox, with no network and a read-only root, as a
non-root user in an image with no shell. A Z3 preamble cannot redefine a
built-in name (not,equals,head,nil, …), and the Lean pre-scan
refuses a proof that could step outside its one theorem (commands,
attributes, imports beyond the allowed ones) before Lean starts. While
native_decideis forbidden, a proof that trusts the compiler some other
way (decide +native,bv_decide) is rejected by the axioms Lean reports
for it. - What the gate writes and shows stays contained. Bundles and artifacts
are written with mode 0600 in 0700 directories, under the tenant's own root
on the managed shape, where the footer and the live events give paths
relative to that root.crewhaus provabilityreplaces control and bidi
characters before it prints, and Hangar masks each bundle with its harness's
own scrubber before it serves one.
Permission rules can no longer be dodged
- A deny or ask fires when any argument it is about matches. A scoped
deny such asalwaysDeny RemovePath(.git/**)in front of a bare
alwaysAllow RemovePathmissed as soon as the call carried one more
argument, or left out one the tool fills in (EnvFileUpsertwith nopath
writes.env). Rules now see defaults and are checked against the input
the tool will actually run on. - A deny that fired in 0.7.0 still fires. Rules now read the place a
tool acts on, but a deny or ask also still reads a call as 0.7.0 did, and
fires when every text value in it matches:alwaysDeny EventQuery(sess-prod-*)still stops a call that names only that session.
An allow reads the place alone. - A scoped allow can no longer be widened.
Write(src/**)authorised a
write to.crewhaus/settings.json— and so standing permissions on the
next run — through a decoyfile_paththe tool never reads, through
src/../, or through a symlinked directory. None of these pass now, and a
path outside the workspace never satisfies a scoped allow. An allow on a
number grants only the numbers it names:SmsSend(+1555?1234567)no longer
grants+1555-1234567, which goes to+15551234567. - On macOS and Windows, a deny holds in any letter case.
alwaysDeny Read(**/.env)did not stopRead(.ENV), which opens the same
file. A path is matched under the name the file is stored with, and a deny
or ask ignores case where the filesystem does. Linux is unchanged. - Every spelling of a destination meets a deny. A deny on
https://evil.example/**missedhttps://x@evil.example/, a trailing dot,
another port and an IPv4 address written as IPv6. A deny on an email
address missed another letter case or a+tag, and a chain address another
hex case. A deny on a phone number missed(+1)5550100000and the other
spellings SmsSend sends to it, and a deny on a host that WaitForPort,
DeliverabilityCheck or FederationDiscover reaches missed it written another
way:10.5is10.0.0.5,münchen.deisxn--mnchen-3ya.de, and
WaitForPort probeslocalhost,0.0.0.0and::on 127.0.0.1 or ::1.
Numbers and hosts are now read the way the tool reads them, in the call and
in the rule:alwaysDeny SmsSend(+1 555*)covers+15550100000, and
alwaysDeny DeliverabilityCheck(münchen.de)coversxn--mnchen-3ya.de.
A deny that names a host covers it over http and https
and on any port, unless it names a port; one whose host is a wildcard
(http://**) keeps to its scheme and port, as an allow does. A path deny
written through a symlinked spelling of the workspace (/tmpfor
/private/tmp) holds too. - A deny on a place the call leaves out still fires.
alwaysDeny KvDelete(.crewhaus/state/**)now stops a call that leaves the
store directory to its default, for the store readers as well as the
writers. The same holds for the git branch tools' and RunBuild/RunTests'
cwdand DependencyAudit's OSV endpoint. An allow scoped to a record, such
asKvSet(scratch/*), keeps covering ordinary calls. - A deny on a directory also covers the directories above it, for every
tool that walks, lists or dumps what a directory holds, read-only ones
included.alwaysDeny RemovePath(src/prod/**)now stopsRemovePath src,
alwaysDeny Grep(secrets/**)stops a search of the whole workspace,
including one with no path, andalwaysDeny *(secrets/**)stopsTree ., a
StateExport of the workspace, and the same in FindFiles, DiskUsage,
TodoScan, SecretScan, ChecksumVerify and the other walkers. A deny on a
harness's own records (session logs, the approvals ledger, settings, eval
history, the watch-me and routing stores) also stops the tools that read
them, such as PermissionsSuggest and ApprovalStatus. A git tool that takes
a path but is given none (GitDiff, GitLog, GitShow) counts as the whole
repository, wherever it runs. A path that names a file is just that file.
Grep, Tree, FindFiles, SecretScan and the code scanners skip hidden names
unless the call asks for them, soalwaysDeny Tree(.env)still allows
Tree .and stopsTree . includeHidden; DiskUsage, ChecksumVerify and
the harness finders read hidden names, so a deny on.envstops their
whole-workspace runs. - A rule reads everything a call can reach. A Glob pattern stands for
every path it can list, including a hidden name it spells out
(alwaysDeny Glob(secrets/**)stops**/*and*/.env). A path
given to a git tool is literal, sosecret*cannot reach around a deny on
secrets/**. A KvList prefix stands for every key that starts with it. A
GitHub or GitLab search is scoped by itsownerandrepo, and a deny on a
repository also stops an owner-wide or unscoped search. TempDir is read as
the directory it makes (base/name, under.tmpby default) and, when
clearempties it first, as everything in it, soalwaysDeny *(secrets/**)stops it from emptyingsecrets/. - A command rule decides on the program that really runs. RunCommand,
RunPipeline, Retry, ProcessStart, RunBuild, RunTests, Format, HooksManage
and Bash are read with the directory they run in, the environment they set
(PATH,BASH_ENV,NODE_OPTIONSand the like) and thecds before each
command, so a deny onscripts/release.shholds when the call runs
./release.shfromscripts/. A program or script named by a path is read
where the system finds it, through..and symlinked folders:
alwaysDeny RunCommand(deploy.sh)stops./scripts/../deploy.shand the
absolute path, andalwaysAllow RunCommand(./scripts/**)no longer grants
a script that a..or a link takes out ofscripts/. The same holds
afterenv -Corsudo -D, for a program found on a PATH the call sets or
written with$PWD, and for a script handed to an interpreter (sh,
node,bun,python3, …) or fed to a shell (sh < x.sh). node,
bun runand a script whose#!line names node settle a..before
they follow links, so they can open a different file than a shell would:
such a word is read both ways, a deny or ask fires on either, and an allow
must cover both. A program reached through a link to the file itself
(node_modules/.bin/eslint) is still allowed by its link name, and a deny
on where the link leads fires too. On all of these but Bash a deny also
reads every other path in the command where it lands
(find . -exec link/../run.sh ;). Write a deny's path without./: one
written with it can miss the same program written without the./or as
an absolute path (see Known limitations). A scoped command allow does not
follow the call into another directory. - A Bash rule reads each command in the line.
alwaysAllow Bash(git *)
allowedgit status && rm -rf build, andalwaysDeny Bash(rm -rf **)
missed anrmthat came second. A line is now split into the commands it
runs, at&&,||,;,|,&and newlines, respecting quotes and
comments. An allow must match every command, or be written as the same
chain (Bash(cd ** && make *)matchescd build && make all, each part
against the command in its place); a deny or ask fires on any of them, or
on the whole line as before. A deny also sees a command through
env,sudo,xargsand similar wrappers, insideeval,sh -cand
$(…), without its quotes, and through variables the line sets. A line
whose commands cannot be read from its text —$(…),$((…)), backticks,
a$'…'string, a here-document, an unterminated quote, afororcase
block — is allowed by no scoped rule, so it asks; one whose program the
text does not name (read x; $x -rf build) also sets off every deny and
ask. The Shell tool is
read the same way, and a deny on RunCommand or another argv tool reads the
line it hands tosh -c. - Text handed to a shell is read as the commands it runs. A call that
hands a shell text on its input (argv: ["sh"]withstdin: "rm -f x",
on RunCommand, Retry, ProcessStart or a RunPipeline step) meets
alwaysDeny RunCommand(rm *)assh -cdoes. In a Bash line, a shell that
reads its commands from a pipe (curl … | sh) sets off every deny and ask.
Each shell's options are read the way that shell reads them, so an
option's argument is never taken for the script. A program or script
written under the shell's own directory any way but a plain leading$PWD
(${PWD%/*}/x.sh,$OLDPWD/x.sh,$(dirname "$PWD")/x.sh) sets off every
deny and ask. - A path read from another argument's directory cannot climb past a
rule. When a tool reads a path relative to another argument (a git or
formatter path relative tocwd), a path that is absolute or has a..
is read as outside the workspace: no scoped allow grants it, and every
deny or ask fires. The program opens it from that directory, following a
link before the.., so through a symlinked folder it reached another
file than the rule checked:alwaysDeny Format(secrets/**)missed
../x.tsrun from a link intosecrets/. Lint, Format and RunTests now
refuse such apathsentry, naming it. - A path names one place at any length. A very long path could be read
as one file by the permission check and as another by the tool, so an
allow on one name approved a change to a different file; StateImport could
write a deeply nested entry above where it said; and SpecPin and
DeployRollback judged a very deep registry folder by the wrong parent.
Each now acts on the place the path names. The check reads a path longer
than any filesystem can open as outside the workspace, and most tools
refuse it before resolving it, with its length and the limit; the core
file tools (Read, Write, Grep, Stat, ReadLines, Tree and the like) still
report it as outside the workspace, or as a read or write that failed. - A deny on a secret holds however the reference is written. SecretLookup,
SecretRotate and EnvFileUpsert read afile:orenvfile:reference as the
file it opens, soalwaysDeny SecretLookup(file:secrets/**)now stops
file:./secrets/token,file:pub/../secrets/token, a link intosecrets/
and a reference with a leading space, and path rules such as*(secrets/**)
apply too. A bareAPI_KEYorenv:API_KEYis also read as the files it is
loaded from (.env.local,.env), soalwaysDeny *(.env)stops it, while
an allow naming the reference as the call writes it still grants it. A
deny or ask also reads what a tool copies from: EnvFileUpsert's and
SecretRotate's source reference, IndexBuild's and DatasetPut's files,
EmailSend's attachments, InvoiceRender's template and the test files
RunTests is limited to. - A rule reads which service, and whose copy, a call acts on. A
code-host call can name its own API inbaseUrl, and a Chat call names
apiBaseUrl, so a rule now reads the repository or channel with that
API's origin in front (https://ghe.example.com/acme/app):
alwaysDeny IssueCreate(https://ghe.example.com/**)fires, including on a
call that names nobaseUrlbut goes there. RateLimitStatus is read as the
API it reports on, the way the tool reaches it, so a deny on that API or its
host stops it however the call picks or spells it (…/api/v3/is
…/api/v3). A ChatPost through an incoming webhook, which ignores its
channel, is read by the variable that names the webhook, and WebhookPost
byurlEnvwhen it gives one. A deploy call that names atenantacts on
that tenant's own copy of the pin and is read as<tenant>/<name>/<env>. - A headless approval holds only where it was granted. A parked call
records where each path, program and script it names lands. If a link on
the way has moved by the time it resumes, the call is asked again, naming
where it was approved and where it lands now, instead of running where you
never approved it. The Slack card, the text fallback and
crewhaus approvals showsay where a parked call lands. A call you refused
stays refused wherever it now lands. - A permission pattern can no longer stall the daemon. A rule with
several*, matched against a long command, could block the event loop
indefinitely. Patterns now match in time proportional to the input, and
match exactly what they matched before. - A sub-agent written to
.crewhaus/sub-agents/can only narrow its
parent. Any agent with a file-write tool can add a file there while it
runs. Itspermissionsreplaced the parent's rules — enough to lift an
alwaysDeny— and amodel_poolcandidate'stoolConfigsin it replaced
the operator'stool_config. Such a definition now runs under its own rules
and the parent's together, only on models the spec names, and is read
without following links;toolConfigsandinherit_bypassthere are
ignored with a notice. To grant a sub-agent more, declare it under
sub_agentsincrewhaus.yaml.
Plan and auto mode
-
Plan mode no longer runs the project's own programs. Typecheck, Lint,
FormatCheck, Diagnostics and CliVersionPin were marked read-only, so plan
mode ran them without asking — and the program they run is the project's (a
linter config, a binary innode_modules/.bin), so planning in a cloned
repository ran its code. They are no longer read-only, and they run without
your API keys and tokens in their environment. CliVersionPin now reads a
CLI's version from itspackage.jsoninstead of running it. -
Plan mode keeps the sandbox floor: a read-only tool that needs a
sandbox is refused when none is available, as auto mode already did. -
NODE_ENV=testno longer switches off the justification check. The
built-in rule check, which the same model that picks a tool can talk past,
was accepted on every justification-gated call wheneverNODE_ENV=test
was set, so a compiled bundle orcrewhaus runstarted on a host that
exports it (a staging box, a CI job) let those calls through. Only the
test runner itself is exempt now; set
CREWHAUS_ALLOW_RULE_BASED_JUSTIFICATION=1to accept the rule check
anywhere else. -
OpenExternal asks in auto mode, and every call carries a justification:
an app it opens keeps running, and a page opens with your cookies. It also
refuses more files a desktop runs or follows on open (.fileloc,.jnlp,
.mobileconfig, installers, disk images and others). -
MCP tools can be marked destructive, and a server that says a tool is
destructive is believed. In auto mode every MCP call ran without asking,
a remotedelete_repoincluded, andmcp_servers.<name>.tool_flags, the
setting meant to change that, failed to compile. It now takes effect, and a
server's owndestructiveHintmakes that tool ask in auto mode. Flags and
hints can only tighten:readOnlyHintis ignored, andtool_flagsno
longer acceptsreadOnly.mcp_servers: github: transport: stdio command: github-mcp tool_flags: defaults: { requireJustification: true } per_tool: { delete_repo: { destructive: true } }
-
A deny on MCP tools written the documented way fires.
alwaysDeny mcp__github__*matched nothing, because the tools were
registered under another name, so in auto mode the call simply ran. -
Thredz
inbox_pollis no longer treated as a read, since it
acknowledges what it fetches: plan mode refuses it, and default mode asks
unless a rule allows it. -
CREWHAUS_SANDBOX=noopturns code execution off in every mode. Under
--permission-mode bypass, or with a stray space or CRLF in the value, the
model's code ran on the host.
Files stay inside the workspace
- Tools no longer write through a link or file planted in the workspace.
A symbolic link at a file's name, at a temp name derived from it, or at a
parent directory could redirect a write outside the workspace. Writes now
go through a randomly named temp file created exclusively, and a link that
leads out is refused, naming the file. This covers the file tools (Write,
Edit, CopyPath, MovePath, SplitFile, ConcatFiles, GoldenUpdate,
DownloadFile, InvoiceRender, IncidentBundle, EmitTraceEvent), the wiki,
memory and continuity stores, datasets, eval baselines and history, the
audit log, and the runtime's own files in.crewhaus/: sessions and
transcripts, the approvals log, settings, routing, dream state,
prompt-cache records, session summaries, watch-me data, incident captures,
alert history, recorded tool cassettes and encryption keys. A link that
stays inside the workspace is still followed, and.crewhausitself may be
a link to state kept elsewhere. - Reads no longer follow a link out of the workspace. Package manifests,
lockfiles, coverage reports, dataset records, eval results, the session logs
PermissionsSuggestreads and the.envthatApprovalStatusreads are
not read through a link that leads out; the tool reports what it skipped. - A named pipe or device no longer freezes the harness. Read, Edit,
ReadImage and the listing, document, table, e-invoice and secrets tools,
and plugin and skill loading, refuse a FIFO or other special file instead
of waiting on it. - Archives cannot plant files past the workspace. ArchiveExtract follows
link chains the way the operating system does, checks what the extractor
actually wrote, refuses FIFO, device and socket members, and caps what it
extracts (maxBytes, 1 GiB by default). ArchiveCreate stores a link as a
link in zip archives too. - Copying or moving a link keeps its reach. CopyPath and MovePath judge
each link from where it will sit: one that would point somewhere new
outside the workspace is refused, and one that points exactly where it did
(a virtualenv's interpreter) is kept and listed inoutsideLinks. CopyPath
refuses a symlink under its destination, and MovePath never deletes the
destination before the move succeeds. - Git tools work only on the workspace's own repository. A workspace
inside a larger checkout, a planted.git, or a repository that borrows
objects or refs from outside is refused. The read-only git tools and
DiffLint no longer run programs a repository's own config names (fsmonitor,
diff drivers, textconv, filters, signature programs, hooks), or run git
inside a submodule. GitApplyPatch refuses a patch that names a file outside
cwdor creates a link pointing outside the workspace. - TrashPath uses only real trash directories you own and never
overwrites what is already in the trash. PowerAssertion keeps its
record in a private per-user directory, and checks a process is its own
before signalling it.
Credentials
-
The model no longer chooses which environment variable is sent as a
credential. Tools that took a variable name from the call now read only
names you list. An unlisted name is refused the same way whether or not it
is set.Tools Where to list the variables HTTP authprofilestool_config.http.allowed_auth_envs(each can be bound to origins)WebhookSign, WebhookVerify tool_config.http.allowed_signing_envsNotify tools tool_config.notify.allowed_secret_envs(a provider's ownauth.envVarworks unlisted)Code-host tools tool_config.codehost.token_env, ortoken_envsfor severalSignPayload, VerifyPayload, Pseudonymize, PiiRedact, RedactForExport tool_config.secure.key_env_varsObjectPresign a profile under tool_config.objectstore.credentials; credentials are no longer argumentsEnvInspect's revealtool_config.proc.env_reveal; a key, token or password is never shown -
A credential stays with its destination. Fetch no longer sends a
call's Authorization, Cookie or custom headers to another origin it is
redirected to. HttpPaginate sends your credential only to the origin you
named. A code-host token goes only tobase_url's host. ImageGenerate sends
OPENAI_API_KEYonly to api.openai.com unlessOPENAI_BASE_URLapproves
another origin. -
A credential is not repeated back. A server that echoes a credential,
even escaped (\u002B,%2F,/, nested JSON, UTF-16), has it
replaced with<redacted>; DownloadFile will not save such a body, and an
HttpWaitFor condition cannot be used to guess one. RPC and knowledge-source
URLs, remote URLs in GitRemoteList, MCP server arguments in SpecSummarize
and SpecDiff, and object-store and image endpoints are shown without their
secrets, and errors name the origin or the variable, never the value. -
Programs the project supplies run without your credentials. Typecheck,
Lint, FormatCheck and Diagnostics get an environment without
credential-shaped variables or URLs that carry a password. RunTests,
RunBuild and Format still get your full environment. -
Secrets in
tool_configcan stay out of the spec. A value written
$UPPER_SNAKEis read from the environment when the harness starts, as
mcp_serversvalues are, and is never repeated in an error.
Where tools send
- Every tool that sends to a place the model picks gets the strict egress
check. Fetch and WebFetch blocked a call carrying text from a tool
result, an MCP response or a sub-agent; HttpRequest, HttpBatch,
GraphqlQuery, WebhookPost, EmailSend, SmsSend, ChatPost, the issue and
pull-request writers, DownloadFile, OpenExternal, HttpPaginate,
PackageManifestVerify, the RPC readers, MCP calls and the other tools that
take a URL or recipient from the model only logged it. They now block it.
Tools that send to a place you configured (SendMessage, WebSearch,
ImageGenerate, a code host's reads) still only log. To relax one sink for a
deployment, passresolveSinkScopeto the runtime. - Tools that reach the network say so, so egress checks and permission
rules see them: the EVM reads and EvmSimulate, generated contract tools
(whose writes now need a justification), Retrieve, the browser's Type, Key
and Click, FindElement, and Recall and wiki ranking when the embedder is
hosted. - An id cannot move a request. ChatPost, ChatUpdate, ChatDelete,
ChatReact and DeliveryCheck refuse an id of.or.., which could send
the request to another endpoint. - Destinations need your list. SmsSend and PushNotify reach only the
numbers and targets you list, and send them as the list compared them: a
number in E.164 form (+15550100000, not+1 (555) 010-0000), a target
without surrounding spaces. WaitForPort probes this machine unless you
list the host, and refuses an IPv6 address that resolvers read differently:
::ffff:0177.0.0.1passed as this machine, and macOS dialled 177.0.0.1.
WebFetch never looks up a host outsideallowed_domains.
Token and multicall batches go only to the canonical Multicall3, or the one
intool_config. - A redirect cannot move DependencyAudit or a chain read. Both refuse
any redirect, even to the same address, and fail with an error saying
where it pointed. DependencyAudit re-sent your lockfile's package list to
wherever the OSV API or a mirror redirected it, even to a server on no
allow-list, and a chain node could send a read to one yourchainsblock
does not name. UnderrpcPolicy: fallbackthe next node is asked. - A
model_poolcandidate'sallowed_originsnarrows what the chain
readers and FederationDiscover reach for that model; it never widens the
agent's list. For other tools a candidate's block replaces the agent's, and
compile notes when it reaches more hosts.
Crafted input can no longer freeze or exhaust the harness
- A pattern a caller supplies runs under a deadline, off the main
thread. Grep, AcceptanceCheck, the Golden tools, ContentPolicyCheck,
GlCodeSuggest, TableQuery, JsonQuery, RegexExtract, RuleClassify, the
routers and scorers, Assert, JsonSchemaValidate, BundleSizeCheck,
FlakyTestDetect, DiffLint, WaitForOutput and AstQuery could freeze the
process on a runaway pattern, or report "no match" when the regex engine
gave up. A pattern that cannot finish is now "undetermined": a check that
depends on it fails, and a search names what it could not scan. Patterns
known to backtrack without bound are refused with the reason. - Built-in parsers take time in proportion to their input: HTML
selectors, Markdown, sitemaps and feeds, OFX statements, CI logs, mail
dates, PDF text, test-runner output, ABI and typed data, JSON Schema, and
glob and.gitignorepatterns. A small input can no longer decode into
millions of values. - Long runs in text no longer slow a tool to a crawl. A long run of
spaces, blank lines, slashes, digits, braces or repeated phrases made many
readers scan the same text again and again: command, build and test
output, commit messages, YAML and workflow files, manifests and lockfiles,
.envfiles, PDFs, subtitles, colours, CSV cells, SQL statements,
date-timechecks, feeds and event streams, the PII, secret and
prompt-injection scanners, the permission check's reading of a call's
directory, PATH, URLs and recipients, model replies read by the planner,
the justification judge and the optimizer, and the Hangar's views. Each
now reads its input once. Tools that compare many items with each other
(SchemaDiff, FactCrossCheck, ImportCsv and ImportJson, the SQL tools,
CostBasisCompute, ConsensusVote, the registry tools on a manifest with
thousands of dependencies, and a session that starts with a large union
schema) no longer slow down sharply as the items grow. - Archives and documents cost what their bytes allow. Listing, checking
and extracting an archive stays fast with very deep member paths, many
links that lead through one another, or many top-level entries, and a tar
with a malformed extended header no longer hangs. Zip data that many
entries share is inflated once, a zip link whose stored target is
implausibly large is refused, and PptxRead, XlsxRead and DocumentText
refuse a zip whose entries overlap. - Paths are checked in time proportional to their length, by the
permission check and every file tool, including a path below folders that
do not exist yet. - A compressed reply costs at most the tool's byte cap after decoding. A
small gzip or brotli body could inflate to gigabytes in Fetch, WebFetch,
WebSearch, the HTTP, notify, code-host and observability tools, the
crawler, and the chain, registry, container, KYC and discovery readers. - Output is capped as it arrives. Commands, cron, desktop and host-search
helpers and code execution keep what fits and flag what was cut, and a
timeout stops the whole process group. A code-execution container is
stopped and removed at its timeout, on cancel, and when the host exits; a
watchdog stops one whose host was killed outright. - Network reads have a deadline. EVM calls, WebFetch's body and
ImageGenerate also have a size cap, and a DeFi call'stimeoutMsbounds the
whole call. - Large files and results are bounded. IngestDocument keeps to its
budget; JSON depth, HtmlTable, TableJoin, text similarity and timezone
caches are capped; and FactCrossCheck reads only so much of its sources in
one call. The new limits you may meet in Read, Edit, ReadLines,
SchemaDiff, RobotsCheck, Slugify and GlossaryReplace are listed under
Changed. A cut result always says so. - Keys named
__proto__orconstructorare ordinary data in every
tool, and no document or tool name can change how another tool behaves.
MCP servers and plugins
- An MCP server can no longer slip instructions to the model through a
tool definition. Descriptions and schemas are screened at registration:
flagged description text is withheld with a warning, a flagged name or
schema leaves that tool out, and long descriptions are cut. One bad tool no
longer takes down the rest of its server. - Signed plugins verify on every boot path, reading trust anchors from
~/.crewhaus/plugin-trust/*.pemandCREWHAUS_PLUGIN_TRUST_ANCHORS; before,
no boot path had an anchor.crewhaus plugins installchecks the
publisher's signature, installs the version you asked for or nothing, and
delivers the manifest only. A signed plugin runs exactly the bytes its
signature names, and a signednotAfterexpires it. Unsigned plugins still
needCREWHAUS_PLUGIN_ALLOW_UNSIGNED=1, which now warns on every boot. - A plugin stays in its folder and its lane. Its
index.jsandskills/
must live inside its own directory; itsengines.crewhausis checked; it
must be the plugin the registry lists, at its pin; it cannot take a
crewhaus tool's name; its tools reach only the host tools its
permissions.toolslists; and a tool definition with a mistyped flag or
schema stops the plugin at boot, naming the field. - The boot says what a plugin is not held to: plugins run with
crewhaus's full access, sopermissions.fs,netandsecretsare not
enforced, and a plugin's channels, models, graders and emitters have no
effect yet.
Known limitations
Provability
- Each rung proves what it says and no more. A well-formed reference to a
page that does not exist passescited.groundedshows the source says
what the claim says, not that the source is right.checkedshows a tool
computed the value, not that it was the right computation to run. At
proven, whether the formal statement means what the prose says is shown
beside the claim, never checked. - A source the run wrote itself still grounds, marked as such. Text
written byBashorRunCommand, by an earlier session, or through a place
the gate cannot map to the one cited (a repository path behind a raw-file
URL) is not traced back to its writer. BashandRunCommandcan still write the session log, since nothing
can see where a shell command writes, and--permission-mode bypasslifts
the permission-level floor. Closing this needs keyed integrity on the log
lines.haltstops at the first failing verdict. No setting retries an answer
and then refuses it if it still falls short.- The claim extractor is a heuristic. An answer in which it finds no claim
passes as vacuous only when it carries no citation marker and no figure
outside a question, but it still misreads some sentences: a closing courtesy
line with three content words is a claim, and some finding headlines
(## Refunds now take 5 days) read as titles. The@crewhaus/claim-ledger
README lists the rest. - Where the gate runs. Crew accepts the block without gating on it, and
pipeline, workflow, graph and the other shapes reject it. Research gates the
report once, withhaltornoteonly, and cannot reachcheckedor
proven. The Cloudflare Worker (compile --emit-as cf-worker) runs no
gate either, and warnsaccepted-but-unwiredon the block. - On the managed shape, a
haltanswers with an error and noproof
field (the bundle is on disk, and itsprovability_gradedevent streams),
and Hangar's proof route reads a tenants root only inside the harness
directory. The console has no rung badge or bundle viewer yet. crewhaus provability checkis not the live gate. It sends no HEAD
request, asks no waiver judge and ignoresscope: turn, and it reads a
cited workspace file straight from disk, where the gate needs the run to
have read it. It says so when it runs.- The sandbox images are published for linux/amd64. An arm64 host runs
them emulated, which is slower. - Only the built-in proof checkers run.
formal.checkerstakes
builtin,smtandlean4, and a plugin cannot register a checker of its
own yet.
Permission rules and tools
-
Fetch's POST, PUT and DELETE, and ImageGenerate, still run unasked in
auto mode. Neither tool is marked destructive, so auto mode runs a
request that changes a remote service, or an image call that is billed,
without asking. A fix is planned for a later release. Until then, add
{ type: alwaysAsk, pattern: Fetch }and
{ type: alwaysAsk, pattern: ImageGenerate }topermissions.rules, or
leave the tools out oftools:. -
A URL rule is checked on the URL a call names, not where the tool goes
next. Redirects, and the next pages HttpPaginate follows, are checked
against the tool's own allow-list (allowed_origins, or WebFetch's
allowed_domains), not your rules, in every mode, and a rule on part of an
origin, such as a path, is not checked on them at all. This covers the
HTTP tools, Fetch, WebFetch and HealthProbe. WebFetch has no allow-list
until you set one. Keep an origin you deny off the tool's allow-list, list
only origins whose redirects you trust, or deny the tool. -
A deny on a command is a list, and a list has gaps. It reads each
command a line runs, through wrappers,eval,sh -c, the line's own
variables and itscds, and where each program and script lands. Its gaps:- In a Bash line, a program another program runs from its arguments
(find -exec,git -c alias…,make) and a file a program opens from
its arguments (cat link/../x) are read as written. - A command
xargsruns gets more arguments from its input than the line
shows, soBash(rm -rf **)missesfind build | xargs rm -rf. Write
Bash(rm*). - A deny with a leading
./(RunCommand(./secrets/**)) misses the same
program or script written without the./(secrets/run.sh, or
pub/lnk/run.shthrough a link intosecrets/) or as an absolute path.
Write it without:RunCommand(secrets/**)meets each of these. - What a script, or code handed to an interpreter other than a shell
(python3 -c,node -e, or on its input), does once it runs is not
read. - A file an interpreter loads through an option written with
=
(node --require=./x.js) is read as written. - A word read after a
cdthat cannot be followed from the text
(cd "$X",cd $(…),cdto a pattern,cd -, aCDPATH), or one
that starts with~or$HOME, is read as written, and a headless grant
for it is not bound to where it lands. - A deny written across more than one command's words
(Bash(true && secrets/**),Bash(A=1 secrets/**),
RunCommand(sh -c sh secrets/**)) is matched on the text as written.
Write it on one command:Bash(secrets/**).
For Bash, allow what may run rather than deny what may not.
- In a Bash line, a program another program runs from its arguments
-
A command allow reads a program's arguments as written.
alwaysAllow RunCommand(cat src/**)also grantscat src/../.env, since
**matches.., andcat src/lnk/xthrough a linked folder; a Bash
allow does the same. On RunCommand and the other tools that take an argv,
a deny reads where each argument lands, so deny what must stay out
(alwaysDeny RunCommand(**/.env)). -
Tools that open one file named in the call are matched on the call's
text. ReadLines, PdfText, TailFile and the other single-file readers
declare no place, so a deny onsecrets/**does not follow a link into it
(ReadLines pub/lnk/token). Deny the tool, or keep links into a denied
folder out of the workspace. -
A tool that reads every file in a folder follows a file in it that links
elsewhere in the workspace, and a deny on the link's target does not stop
it: the session-log readers (EventQuery and the other observability
readers, TraceQuery, SessionSummarize, CostSummarize, DatasetMine,
SpecAdvise, EvalCoverage), DocsSymbolCheck, ChecksumVerify,
MigrationStatus and CiWorkflowAudit, including its default
.github/workflows. Keep such links out of the folders they read, or deny
the tool. -
A deny on a hidden file also stops the session-log readers. EventQuery
and the other tools that read a folder of records whole are read as
everything in it, though they open only their own record files. Before you
upgrade lists the calls this stops and how to keep such a rule on file
readers. -
A deny naming a project or harness file does not stop the tools that
read it by name. DependencyList, DependencyOutdated, DependencyAudit,
PackageScripts, LicenseAggregate, PackagePublishPreflight and
RegistryOutdated read manifests and lockfiles, and the fleet tools read a
harness's spec and bundle. Deny the tool. -
IndexSearch reopens the files an index recorded, so after adding a
deny, rebuild any index built before it. MarkdownLinkCheck still
reports whether a link target exists anywhere in the workspace, a denied
one included. -
A container image is matched as the call writes it.
ContainerImageInspect and ContainerImageTags readalpineas Docker Hub's
docker.io/library/alpine, but a deny on one of those spellings does not
stop the other. Deny each spelling, or the tool. -
A rule on an address does not meet a name that resolves to it. Rules
are checked before anything is looked up, so
alwaysDeny WaitForPort(10.0.0.5)does not stop a probe of a listeddb
that resolves there. Deny the name too, or the tool.localhostis the
exception: a deny on 127.0.0.1 or ::1 covers it. -
Git tools that take no path (GitStatus, GitStashPop, GitCherryPick …)
are matched on their repository. Taking only acwd, they change or
report files no path rule reads. Deny the tool to keep it off. -
A notify rule names who receives the message, not the SMTP relay, the
SMS or push provider, or the Chat bot token it goes through;tool_config
bounds each of those. A post through a webhook variable (ChatPost's
webhookUrlEnv, WebhookPost'surlEnv) is read by that variable, never
by the URL it holds: deny the variable. -
Some of the runtime's own bookkeeping in
.crewhaus/is not yet
link-contained: the Hangar and supervisor ledgers, research fetch and
citation logs, the feedback queue, the experiment ledger, and the files
hoist-modelsandmemory migraterewrite. -
Plugins are not sandboxed, and
plugins installdelivers the manifest
only: you put the plugin'sindex.jsin place yourself. Delivering plugin
code is planned for a later release.
Corrections to the 0.7.0 notes
Some statements in the 0.7.0 entry were wrong:
- "It can now call 549" held for the cli shape only, because no target
emitter changed. From 0.8.0 every shape that runs tools can call them, with
the exceptions under Fixed, andexport claude-plugincarries none. - "Every other one is local work": the rest make no model call, but many
reach a service you configure — a chain node, GitHub, a chat or SMS
provider.crewhaus tools showmarks each oneio:network. tool-tableis not pure computation: it reads CSV and JSON files from the
workspace, andall-computedoes not include it.- The release added fifty-six tool packages plus
@crewhaus/tool-categories,
and the grouped list left outtool-http(HTTP requests, downloads and
probes). - Not every chain package refuses
eth_sendTransactionat an RPC seam: the
four that talk to a node refuse it through a read-only allow-list, and
tool-onchainnever talks to one. - A category leaf usually maps to one package, not always:
all-httpis
tool-httpandtool-fetch, andall-mediaistool-media,tool-image
andtool-image-generation.
What's Changed
- docs(changelog): two of the 549 do call a remote model by @crewhaus in #478
- feat(capability): a harness can see the tools it does not have by @crewhaus in #480
- fix(cli): the approvals log is not a session by @crewhaus in #479
- test(tool-http): HttpBatch no longer races a 200ms deadline, and now catches a cancel by @crewhaus in #481
- fix(cost-tracker): Opus 5.5 and Sonnet 5 were priced and sized as older models by @crewhaus in #482
- fix(tool-http): DnsLookup's budget is its timer, not the wall clock by @crewhaus in #485
- 0.7.1: make the 0.7.0 tools safe to grant by @crewhaus in #488
- fix(runtime-core): recalled memory is classified before it reaches the system prompt by @crewhaus in #483
- fix(compaction): end the compacted history on the user's message, not the summary by @crewhaus in #484
- fix(tool-http,tool-obs,tool-notify,tool-codehost): no work starts once a deadline's signal has aborted by @crewhaus in #487
- fix(runtime-core,tool-wiki): an injection split across two recalled entries is redacted by @crewhaus in #501
- test(tool-mcp): the exit tests run this checkout, offline, and count the ladder by @crewhaus in #500
- fix(mcp-host): closed is terminal, even for an attempt already in flight by @crewhaus in #502
- fix(prompt-injection-detector): read the whole text, window by window by @crewhaus in #503
- 0.8.0: the provability release by @crewhaus in #504
- chore(release): crewhaus 0.8.0 by @crewhaus in #505
Full Changelog: v0.7.0...v0.8.0