Behavioural contracts
A contract is a file the customer owns and versions, written so a compliance officer can read it and say whether it matches the policy.
Behavioural contracts
A behavioural contract is a file that says what has to be true before an action is allowed to happen. The customer owns it, versions it like a document, and can hold it against the policy it came from.
The model never sees it. Contracts are YAML compiled to JSON at install, the gate reads them, and the agent is not told what they say.
Two files, two jobs
A contract set carries the rules. The mandate carries the things those rules compare against, and it says which sets are loaded.
# ~/.coco/mandate.yaml
id: coco.developer
status: active
unknown_tool: BLOCK
escalate_fallback: deny
limits:
max_files_written: 40
packs:
- baseline
- sessionA contract set can sit in the directory unenforced until the mandate names it, so a set you are not ready to run costs nothing to stage. The KYC and payments example sets rely on that, and they are in the repository rather than the npm package, because neither has the state it reads on a developer's machine.
One field is the kill switch. Set status to anything other than active and
every call stops, before a contract is loaded.
What a contract looks like
id: session.credential_then_egress
action: send_after_reading_credential
families: [egress, command, browser]
description: >
Governs the pair, not the call. Reading a credential file is allowed.
Fetching a URL is allowed. Doing the second after the first is exfiltration,
and a gate that judges each call on its own cannot see it.
applies_when: tool.is_egress == true
constraints:
- id: no_egress_after_credential_read
rule: session.secrets_read == 0
on_fail: BLOCK
reason: >
This session has already read a credential file, and this call sends
data off the machine. Each half is permitted on its own and the
sequence is notEvery field earns its place. families routes the contract to the actions it
governs. applies_when narrows it to the calls worth checking. Each constraint
carries an id, so a receipt can name the exact check that fired, and a reason,
because a verdict with no sentence behind it cannot be shown to anyone.
description is not decoration. The compiler refuses a contract without one, on
the grounds that a contract nobody can read is not a contract.
The expression language is deliberately small
Rules take comparisons, boolean operators and arithmetic. No function calls, no lambdas, no comprehensions, no attribute access into anything private, and no builtins.
That limit is the point. A rule an auditor cannot read out loud in a meeting is a rule nobody will defend when it blocks something. A compliance officer can read a constraint and say whether it matches the policy, and that is the test the format was designed against.
A rule that reads a name the state does not carry gets nothing rather than an error, so a contract written against state a provider could not supply fails its check instead of crashing the gate.
The compiler refuses rather than warns
coco compileCompilation fails on a rule that will not parse, a verdict that is not ALLOW, BLOCK or ESCALATE, a check with no id, a check with no reason, a duplicate contract id, and a mandate with no id. Each is reported by name at compile time rather than discovered by a block in the middle of someone's work.
The installer compiles before it writes the hook, so a set that does not compile never gets installed.
Drafting from a policy you already have
Rules that already exist and are already agreed beat rules invented in a meeting. An authoring agent can read a policy document and draft a contract from it, and every drafted check cites the policy file and quotes the sentence it enforces.
coco author --from procedures/onboarding.md --action approve_customer
coco author --describe "no export over 500 rows without a compliance officer"A draft is validated three ways before anyone reads it, and none of the three involves a model. The schema has to parse, every rule has to compile under the safe subset, and the draft is run against fixtures so you can see which way it decides.
Drafts land in ~/.coco/packs/_drafts/, and the compiler does not load that
directory. A draft enforces nothing until a person moves it into a set the
mandate names.
A model drafts the contract once and a person approves it. Nothing about the runtime check involves a model, which is why the same call against the same state always returns the same answer.
Next
- Writing a contract for the full shape
- State reference for every readable field
- The mandate for limits, ceilings and case state