Cognitive Fab › polyx › Get started
Get started

polyx

Mine the rules your agents’ history supports, approve the ones you mean, and have Claude Code check them before every consequential tool call. About fifteen minutes from clone to a guarded session.

git clone https://github.com/cognitive-fab/polyx
cd polyx && npm install
BUSL-1.1, in-house production use granted Node ≥ 22.18 no API key needed also on npm: @cognitive-fab/polyx
01 / See it work

Policy against practice, on a fixture

A synthetic customer-service corpus and its written policy ship with the package. Mine it, then compare what the agents did with what the policy says.

The report has three parts: tribal knowledge (followed, never written), confirmed, and the compliance gap (written, not followed).

node bin/polyx.mjs mine synthetic
node bin/polyx.mjs diff synthetic
TRIBAL KNOWLEDGE — followed, never written (2) 67752a0a7ed0fb92 op-alpha 5/5 Before you offer a goodwill credit, verify the customer's identity — every time, in the same episode. CONFIRMED — written and followed (5) cd9baa630fa9fcfe op-alpha 13/14 Before you issue a refund, verify the customer's identity — every time, in the same episode. ↔ c-refund-verify [exact, proposed for confirmation] contradicting: syn-0008#3 COMPLIANCE GAP — written, not mined (1) c-refund-note Add a note to the case before issuing a refund. VIOLATED 16 of 20 times — your reps ignore this

--out report.html writes the same report as a page for a reviewer who never opens a terminal.

02 / Keep your sessions

Before you mine: keep the history

Claude Code deletes transcripts older than cleanupPeriodDays, which is 30 days unless you set it. Those transcripts are the corpus. Raise the setting first.

Then copy your sessions into the corpus. A re-run only adds: new sessions are copied, sessions that grew are updated, and a session Claude Code has since deleted stays in the copy, which is by then the only one. Re-run it at least once per retention period.

// ~/.claude/settings.json { "cleanupPeriodDays": 365 }
# what it would add; writes nothing
node corpora/link-cc.mjs --dry-run
node corpora/link-cc.mjs

On our own machine, 100 of the first 141 sessions were already gone from Claude Code by the second pass. The corpus still has them.

03 / Mine your own

Your Claude Code sessions

Each candidate rule arrives with its support, the sessions that satisfy it, the sessions that contradict it, and whose practice it is. Rules are mined per project.

Same corpus, same alphabet, same thresholds: the same rule set, every time. There is no model call anywhere in mining.

node bin/polyx.mjs mine cc
8fae3d1e7fc13c05 [proposed] archlens 16/17 own@operator Before you push to the remote, commit — every time, in the same episode. held 16 of 17 times, and it would have stopped the other 1; commit came first every time it mattered

A real candidate from one of our projects. Expect thousands, most of them coincidences. Shortlist the ones that guard something hard to undo, such as a push, a delete or a discard, and that several projects agree on.

04 / Approve

A person marks what is real

The review page shows one rule per screen, in plain language, with the evidence on both sides. Mark it real, not real or narrowed. Nothing is served until someone has said real.

Rules are mined per project. Real everywhere (E) applies one verdict to every project whose own history keeps the rule, and lists the projects where it would only be borrowed.

node bin/polyx.mjs review cc serve
# or from the terminal
node bin/polyx.mjs review cc mark <rule-id> real --everywhere

Verdicts persist across re-mining. review cc pace reports the median seconds per verdict, because a reviewer’s time is the real constraint. On 198 of our own sessions, twelve approved rules became 96 project-level rules in service; see the list.

05 / Serve

The advisor

One call, made before a consequential action: here is the episode so far, here is what I am about to do. The answer is one of four verdicts.

warn: a rule says something should have happened first. recommend: a rule suggests an action. clear: every rule on this action holds. abstain: no rule applies, or one does but a fact is unknown, and it names the fact.

node bin/polyx.mjs serve cc --port 7777
POST /advise { "operator": "...", "episode": { "events": [ ...so far ] }, "considering": "action:git_push" } → { "verdict": "warn" | "recommend" | "clear" | "abstain", "warnings", "actions", "abstention" }
06 / Guard Claude Code

A PreToolUse hook

Add the hook to .claude/settings.json in the project you want guarded, or ~/.claude/settings.json for all of them. Make the path absolute.

On warn the tool does not run, and the model is told why in the rule’s own words, with its support. If the advisor is down, the hook lets the call through and says so. POLYX_HOOK_STRICT=1 blocks instead.

{ "hooks": { "PreToolUse": [ {
  "matcher": "Bash|Edit|Write|MultiEdit|NotebookEdit",
  "hooks": [ { "type": "command", "timeout": 5,
    "command": "node /path/to/polyx/examples/claude-code-hook/polyx-hook.mjs" } ]
} ] } }
polyx: Before you push to the remote, commit — every time, in the same episode. (held 33/35, own@operator; action:git_commit has not happened yet in this episode)

What the model saw in one of our sessions. Read-only tools are left out of the matcher: they are never a decision point.

07 / Other agents

Any framework with a pre-tool seam

The integration is the same three steps everywhere: type the trajectory so far, post what the agent is considering, act on the verdict. What changes per framework is the alphabet, and for Claude Code that ships.

A · In Claude Code

Shipped, sixty lines

examples/claude-code-hook/. The test suite runs it exactly as Claude Code does, so the page cannot drift from the code.

B · In your framework

Same verdicts, your seam

Agent SDKhooks.PreToolUse
LangGraphinterrupt_before
OpenAI Agentstool guardrail
Vercel AI SDKtool middleware
08 / Add Jev (optional)

Rules on what was said

Jev, TypeSafe’s System One model, answers a declared yes/no question about a text. polyx turns a confident yes into a fact such as obs.states_double_charge, and anything less into unknown, never false.

The walkthrough runs in ten minutes with no key, using a recorded answer file. With a key, the order is: declare, dry-run and read every payload, label about 60 examples, derive the bands, then annotate once.

node bin/polyx.mjs annotate synthetic-observable --import   node_modules/@cognitive-fab/polyx-lens/fixtures/synthetic-observable/synthetic-observable@1.jsonl
node bin/polyx.mjs mine synthetic-observable --threshold minInstances=3
node bin/polyx.mjs review synthetic-observable mark 2f663a4cec907cc9 real
node bin/polyx.mjs coverage synthetic-observable
2f663a4cec907cc9 [proposed] op-alpha 5/5 own@operator When obs.states_double_charge = true, escalate to a supervisor. coverage 50.0% (5 answered; 5 as unknown-fact) answered 0 without observation, 5 because of it yield 5 abstention(s) resolved, 0 warning(s) newly raised — nothing else can move

Full walkthrough: examples/jev-observation. With a key: POLYX_JEV_KEY and annotate <corpus> --dry-run first.

09 / Your own policy

Measure practice against your rulebook

Write your policy as a clause set in YAML, one rule per clause. Clauses polyx cannot state as one action preceding another are marked with the reason and reported, not dropped.

For a worked example on public data, see polyx-bench.

node bin/polyx.mjs diff cc --policy my-policy.yaml --out report.html
- id: c-refund-verify text: "Verify the customer's identity before issuing a refund." expressible: true shape: precedence subject: action:issue_refund guard: action:verify_identity

Next