Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

CLI Commands

The quipu binary provides a command-line interface for all operations.

Global Flags

FlagDescription
--db <path>Store database path (default: .bobbin/quipu/quipu.db)

Build identity

quipu --version (also -V or version) reports build identity without loading configuration or opening a database:

quipu <version>
git_sha: <build-commit>
git_dirty: <true-or-false>

The first line retains the version-only format for existing parsers. The second line identifies the source commit, or unknown when built without Git metadata. The third reports the build-time working-tree dirty flag. quipu-server --version (and -V) exposes the same fields with a quipu-server first-line prefix. Compare known build commits to detect differences between releases; equal version strings alone do not establish that two binaries contain the same code.

Commands

quipu knot <file.ttl>

Load RDF facts from a Turtle file.

quipu knot data.ttl --db my.db
quipu knot data.ttl --shapes schema.ttl --db my.db  # With SHACL validation
quipu knot data.ttl --timestamp 2026-03-15T00:00:00Z --db my.db  # Source-true valid-time
FlagDescription
--shapes <file>SHACL shapes file for write-time validation
--timestamp <ISO-8601>valid_from for the facts (default: now). Supply the source event time when ingesting history

Alias: load

quipu read "<sparql>"

Execute a SPARQL query.

quipu read "SELECT ?s ?p ?o WHERE { ?s ?p ?o } LIMIT 10" --db my.db
quipu read "SELECT ?s WHERE { ?s a <http://ex.org/Person> }" --valid-at "2026-03-01"
FlagDescription
--valid-at <date>Time-travel: query as of this ISO-8601 timestamp
--tx <N>Time-travel: query as of this transaction ID
--fork <name>Scope the default graph to a named fork (see quipu fork); unknown or dropped forks are refused

Alias: query

quipu cord

List entities, optionally filtered by type.

quipu cord --db my.db
quipu cord --type "http://example.org/Person" --limit 50 --db my.db
FlagDescription
--type <IRI>Filter by rdf:type
--limit <N>Maximum results (default: 100)

quipu unravel

Time-travel query: view facts at a past point.

quipu unravel --tx 5 --db my.db
quipu unravel --valid-at "2026-03-15T00:00:00Z" --db my.db

Requires at least one of --tx or --valid-at.

quipu episode <file.json>

Ingest a structured episode from a JSON file.

quipu episode deploy.json --db my.db
echo '{"name": "test", "nodes": []}' | quipu episode - --db my.db  # stdin
quipu episode deploy.json --base-ns "https://quarterdeck.internal/ontology#" --db my.db
quipu episode deploy.json --timestamp 2026-03-15T00:00:00Z --db my.db
FlagDescription
--base-ns <IRI>Namespace to mint entity IRIs in (default: the built-in aegis namespace). Lets non-aegis deployments use the episode abstraction
--timestamp <ISO-8601>valid_from for the facts (default: now)

quipu retract <entity-IRI>

Retract facts for an entity.

quipu retract "http://example.org/old-service" --db my.db
quipu retract "http://example.org/alice" --predicate "http://example.org/email" --db my.db
FlagDescription
--predicate <IRI>Only retract facts with this predicate
--timestamp <ISO-8601>Transaction valid-time for the retraction (default: now)

quipu shapes

Manage persistent SHACL shapes.

quipu shapes load person-shape schema/person.ttl --db my.db
quipu shapes list --db my.db
quipu shapes remove person-shape --db my.db

Loaded shapes automatically validate all future writes.

quipu validate

Dry-run SHACL validation without writing.

quipu validate --shapes schema.ttl --data test-data.ttl
quipu validate --shapes schema.ttl --data test-data.ttl --format json

--format json prints one JSON object with conforms, violations, warnings and every result (severity, focus node, component, path, value, source shape, message), in the order the engine reported them and without deduplication. The text form prints only the counts on a conforming run and omits the shape, component and value. The exit status is the same in both forms: 0 when the data conforms, 1 otherwise or on an error.

quipu export

Export deterministic RDF from ROOT or one explicit scope.

quipu export --db my.db                        # N-Triples (default)
quipu export --format turtle --db my.db        # Turtle
quipu export --group-id project-a --db my.db   # provenance group
quipu export --construct 'CONSTRUCT { ?s ?p ?o } WHERE { ?s ?p ?o }' --db my.db
FlagDescription
--format <fmt>Output format: ntriples (default) or turtle
--graph <iri>Export one named graph
--group-id <id>Export entities attributed to one episode group
--construct <query>Export a SPARQL CONSTRUCT or DESCRIBE graph

The three scope flags are mutually exclusive. Omit all three for ROOT.

quipu share

Write a deterministic directory intended for git storage and interchange. First load an identifier-policy catalogue and your data’s shapes into the same store. The examples below assume that setup. Outward shares refuse with exit 2 when no block-tier catalogue is available, exit 1 when a rule matches, and exit 0 when the checked payload is clean.

quipu share --output knowledge-share --db my.db
quipu share --output project-share --group-id project-a --shapes project-shapes --turtle
quipu share --output next-share --parent-share sha256:abc123 --db my.db
FlagDescription
--output <dir>New destination directory (required; an existing path is refused)
--graph <iri>Share one named graph
--group-id <id>Share entities attributed to one episode group
--construct <query>Share a SPARQL CONSTRUCT or DESCRIBE result
--shapes <name>Include one loaded shape set; repeatable
--no-shapesExplicitly create a shapes-free share
--parent-share <id>Record the prior share_id in this lineage
--turtleAdd the derived, human-readable export.ttl view

The three scope flags are mutually exclusive and default to ROOT. By default, the share includes every loaded shape set. An explicit --shapes selection narrows that set. If no shapes are loaded, the command refuses to produce a silent empty bundle unless --no-shapes is supplied. Required payloads are export.nt, shapes.ttl, and manifest.json. The graph payload is sorted and duplicate-free; the manifest hashes the exact payload bytes and uses the anchored transaction timestamp, so unchanged state produces byte-identical output.

quipu status and quipu merge

Compare an incoming share with local ROOT using the snapshot named by its parent_share, then reconnect the two histories with an RDF-aware three-way merge:

quipu status shares/alice-next --db my.db
quipu merge shares/alice-next --actor reviewer --db my.db

The base snapshot must exist exactly once beneath the incoming share’s parent directory. Missing or ambiguous lineage is refused. status reports additions, removals, divergence, and the same structured DecisionRecord conflicts that merge would encounter.

Unconstrained multi-valued predicates use set union. A predicate governed by sh:maxCount becomes a conflict when the merged cardinality exceeds its bound; a delete racing a replacement on sh:maxCount 1 is also a conflict. Conflicted slots are held at their base values and merge exits 2 without writing ROOT. A clean merge applies assertions and retractions atomically, with both the local graph hash and incoming share_id recorded as provenance parents.

quipu stats

Show store statistics.

quipu stats --db my.db

Output: fact count, entity count, predicate count.

quipu reason

Run the Datalog reasoner to derive facts from rules.

quipu reason --db my.db
quipu reason --rules custom-rules.ttl --db my.db
# --reactive needs a non-default feature (see below):
quipu reason --reactive --db my.db   # requires: cargo build --features reactive-reasoner
FlagDefaultDescription
--rules <file>shapes/aegis-rules.ttlTurtle file containing rules
--reactiveoffRegister reactive observer after evaluation. Requires the non-default reactive-reasoner feature; on a build without it, quipu reason --reactive errors and exits non-zero rather than silently doing nothing.

Output shows asserted/retracted counts per rule. Derived facts are written with source = "reasoner:<rule-id>" provenance.

See Reasoner Reference for full details on rule syntax and the evaluation model.

quipu demotions

List unsupported Datalog demotions in a premise graph’s companion:

quipu demotions --db my.db
quipu demotions --graph urn:example:premises --db my.db

The JSON rows identify the retained record, subject, predicate, object, premise graph, deriver source, promotion transaction and invalidation transaction. --graph defaults to ROOT. The command installs the shipped unsupported_demotions stored query if missing; a fresh store returns no rows. After the companion is initialized, HTTP clients can call POST /ask with {"name":"unsupported_demotions"}; for a named companion, add "params":{"graph":"urn:example:premises#inferred"}.

Support loss removes the original triple from first-class standing and retains one quipu:DemotedDerivation reification record. Datalog, OWL and RDFS materializers exclude plane bookkeeping from premises. Ordinary graph queries can still inspect the record; it does not assert the reified triple. Restored support marks it resolved and recreates the derivation only in the companion. Resolved records remain available through graph queries but leave this list. This is truth maintenance, not a promotion or automatic-promotion API. Load the product evidence schema alongside the application’s other shapes:

quipu shapes load demoted-derivations shapes/demoted-derivation.ttl --db my.db

Demotion does not load shapes automatically: installing the first shape set would activate the vocabulary gate for unrelated writes in an ungoverned store.

quipu impact <entity-IRI>

Bounded BFS over entity edges: what is downstream of this entity? With --remove, speculatively retracts the entity (SQLite savepoint, no mutation), re-runs the reasoner inside the fork, and walks the result — “what would break if I removed this?”.

quipu impact http://example.org/traefik --hops 3 --db my.db
quipu impact http://example.org/traefik --remove --db my.db
FlagDescription
--removeCounterfactual: impact of removing the entity
--hops <N>Walk depth (default from DEFAULT_HOPS)
--predicate <IRI>Restrict to these predicates (repeatable)

quipu project

Run graph algorithms over the projected knowledge graph: stats, in_degree, pagerank/ppr, components, louvain, shortest_path.

quipu project --algorithm pagerank --limit 10 --db my.db
quipu project --algorithm pagerank --seed http://example.org/alice --db my.db  # PPR
quipu project --algorithm shortest_path --from <IRI> --to <IRI> --db my.db
FlagDescription
--algorithm <name>Algorithm to run (default: stats)
--type <IRI> / --predicate <IRI>Restrict the projection
--graph <IRI>Project one named graph’s own facts instead of ROOT
--seed <IRI>PPR seed (repeatable; switches pagerank to personalized)
--damping / --max-iters / --tolerancePageRank parameters
--limit <N>Max results (default: 20)
--from / --toEndpoints for shortest_path

quipu render

Render markdown and byte-addressed includes from a stored specification or an ad-hoc query into an owned destination. Supports single-file and per-entity hierarchies, --valid-at, a no-write destination dry-run and local/remote reads. See the render reference for the command, manifest and refusal contract. A remote server prepares bytes; only the client publishes files.

quipu report

Graph health report: hub entities (god-nodes), surprising connections, and suggested competency questions.

quipu report --hubs 10 --surprises 5 --db my.db
FlagDescription
--hubs / --surprises / --questionsHow many of each to return
--type <IRI> / --predicate <IRI>Restrict the underlying projection

quipu repl

Interactive SPARQL prompt.

quipu repl --db my.db

Type SPARQL queries at the prompt. Use :quit or :q to exit.

quipu audit <trace.jsonl>

Check an enforcement trace against the constraint specification in the store — SARC’s T ⊨ Σ.

quipu audit ~/.local/state/hank/metrics.jsonl --db my.db
quipu audit trace.jsonl --json --db my.db
FlagDefaultDescription
--jsonoffEmit the full report as one JSON object instead of readable lines

Exit code 1 when the trace contradicts the spec, 0 otherwise — so a CI job can gate on it without parsing anything.

Four passes run over every record: coverage (is every constraint the trace cites actually in Σ, and does every refusal name one), placement (was each constraint evaluated at a point its class can be enforced at, does the record agree with Σ about its class, and does the layer that actually evaluated it match the aegis:hostedAtLayer the policy claims — SARC I6), outcome (does the response taken match the one declared, at the recorded mode), and attribution (does the record say who is answerable). Every pass is a comparison between two declared values; none of them calls a model.

The I6 check is one-directional. A policy claiming "tool" while a hook in the agent’s own loop evaluated it is a violation — it reads as enforced somewhere an agent cannot route around while being enforced somewhere an agent can. A policy claiming "orchestration" while something stronger enforced it is silent: understating your own robustness misleads nobody in a direction that costs them.

Findings come in two severities and only one of them fails the gate:

  • violation — the trace contradicts Σ. A soft constraint that blocked, a declared deny that only warned under enforce, a record whose declared principal chain disagrees with the process that ran.
  • incompleteness — the trace does not say enough to decide. No principal chain, no declared class, a constraint Σ declares that this window never exercised.

Incompleteness never changes the exit code. A checker that failed the build over a missing planner would be switched off within a week, and then the violations would stop being caught too.

Two limits worth stating before reading a T ⊨ Σ result as reassurance. Coverage is checked in the direction quipu can decide — nothing is cited that Σ does not define — because the other direction, was every constraint that applied evaluated, means re-running the selector against the file as it stood, and quipu has neither by default. The opt-in Git and Yupana passes below supply that evidence. The report counts lines it could not read rather than skipping them, so N line(s) unreadable is always part of the summary: conformance over a window that was only partly read is not conformance.

Git path-policy backstop

quipu audit trace.jsonl --repo . --from BASE --to HEAD --json --db my.db

All three Git flags are required together. BASE is exclusive; HEAD is inclusive. Both resolve to full immutable commit ids before enumeration. The base must be an ancestor. Shallow repositories and ancestry grafts are refused; replacement objects are ignored. The git object reports those ids, commits and changed paths checked, path-policy count, and unresolved coverage. An empty policy catalogue is unproven, not a clean scan.

The additional git-coverage pass reads ROOT’s current action policies with appliesTo path globs. Globs use the same glob::Pattern matching as Yupana tripwires. It checks each commit, not just the window’s net diff: a crossing followed by a revert is still visible. Renames check both old and new paths. The window includes side-branch commits. A merge only contributes paths whose committed state differs from every parent (including mode changes and deletions): inherited content is audited where it was introduced, while new merge content still needs its own evaluation. This also handles octopus merges; comparing against just one parent would either duplicate evidence or miss a new merge change. Policy scope is the supplied repository; use a store containing the policies intended for that repository. Policies introduced after the audited commits also apply: this is a current-policy retrospective audit, not historical policy reconstruction.

A changed matching path needs a conclusive evaluation with the exact policy id, repository-relative path, and full git_commit id in the trace record. Missing evidence produces a bypassed enforcement violation. A path-only deny crossing produces a violation even with a recorded evaluation: a claim that it was blocked cannot excuse the committed crossing. The report includes the commit author and declared Co-Authored-By, Claude-Session, and SHANTY_AGENT metadata for investigation.

Existing pre-edit spools do not emit git_commit; they remain valid for the trace-only checker but do not establish Git coverage. A trusted producer must bind an evaluation to its actual commit; do not backfill every old path record with the current tip. This reader does not authenticate such bindings.

Git mode exits 1 for a violation, 2 when Git coverage cannot be verified (including malformed trace lines, invalid refs/globs, no path policies, or unsupported selector replay without another violation), and 0 otherwise. Trace-only incompleteness retains its original semantics. Without --yupana, selector/predicate policies with matching appliesTo are unresolved and selectors without path scope remain outside the path-only pass.

Replay tree-sitter policies at each commit

quipu audit trace.jsonl --repo . --from BASE --to HEAD --db my.db \
  --yupana /trusted/path/to/yupana --json

The explicit executable must provide Yupana’s audit-rule JSON interface, schema version 1. Quipu loads each policy’s current selector and predicate from ROOT and supplies the exact regular-file blob from each commit to that parser. It never reads the working tree’s contents or follows committed symlinks. Unscoped structural policies are included in this mode; Yupana’s own path-to-language classifier decides language applicability. appliesTo still narrows a policy when present.

A replayed unsatisfied claim is a violation even if its trace claims success. Successful replay does not erase a missing enforcement record. The git object reports selectors_checked separately from paths and commits. Deleted paths, non-UTF-8 or oversized blobs, malformed/ambiguous selector definitions, unsupported grammars, invalid syntax, a missing executable, and protocol or process failures are unresolved. A pure path policy still covers deletions. Source blobs are limited to 8 MiB; each evaluator invocation has a 10-second limit and a 1-MiB output limit. Only tree-sitter selector definitions with regex predicates are supported; entity-grounding and other selector tiers remain unresolved.

This is an agent-invoked audit library/CLI, not an installed scheduler or forge gate. Select and pin a trusted Yupana build: invoking a caller-selected executable does not authenticate that executable or the response it produces.

This catches only changes reaching the selected Git window. Uncommitted edits, ignored files and writes outside the repository remain invisible. Authors, trailers and trace evidence can be forged: this is an audit backstop, not a server-side enforcement boundary. Human edits are audited too, because an absent agent trailer does not prove a human author. Signed exception verdicts and a required forge check are not implemented by this command.

quipu audit inventory

Check the dispatch graph rather than a trace — SARC I7, enforcement completeness.

quipu audit inventory --db my.db
quipu knot shapes/dispatch-inventory.ttl --db my.db   # load the shipped seed first

I7 is a property of the dispatch graph, not of any one constraint: a harness exposes N classes of tool call, and completeness is the question of whether every class that can change state passes through a point where a constraint could stop it. aegis:ToolClass declares each class, whether it is executable, and which governedAt points it traverses.

Findings, in the same two severities:

  • violation — an executable class that traverses no enforcement point and has no aegis:ungovernedReason. An unknown hole.
  • incompleteness — an executable class that traverses nothing but says why: an acknowledged bypass surface. Reported on every run, because a bypass surface an operator has stopped seeing is one they have stopped weighing. Also: a class that does not declare aegis:executable, since whether it needs a point is then undecidable.
  • violation, the other direction — a constraint in Σ placed at a point no declared executable class traverses. It reads as governance in the catalog and can never fire in the deployment.
  • incompleteness, the zero-trust boundary — a class declaring aegis:importsUntrustedState brings content into the agent’s context that has not been through this deployment’s constraints (a sub-agent’s response, an MCP server’s output, retrieved documents). Reported whether or not the class is governed: governedAt says its own actions traverse a point and says nothing about what it returned. No trust predicate evaluates imported content today, so this is an open boundary reported on every run rather than a closed gap. A class that imports and declares no aegis:untrustedOrigin is a violation — an import channel nobody can describe is one nobody can weigh.

An empty inventory is reported as an incompleteness, never as a pass: an unwritten dispatch graph is not an empty one.

shapes/dispatch-inventory.ttl ships the seed for this stack — the edit path and quipu’s own write gate as governed, reads as non-executable, and Bash, Task, CI pipelines, cron, remote shells, a sibling session’s VCS index and a hostile agent as acknowledged surfaces with where each is enforced instead. Nothing derives it from the harness’s actual tool registry, so it can drift from reality the way a prose list does; the difference is that a drifted declaration is a wrong answer to a question something asks rather than a paragraph nobody re-reads.

quipu audit namespace

List the base-namespace predicates episode ingest minted that no loaded shape mentions — namespace drift, in the same shape as quipu audit inventory.

quipu audit namespace --db my.db
quipu audit namespace --graph urn:example:tenant --json --db my.db

Exits 0 whatever it finds, and refuses nothing. Every key in an episode node’s properties map becomes a predicate in the base namespace via sanitize_iri_local, with no shape governing which keys are admissible, so agents writing free-form properties mint predicates indefinitely and nothing reported the drift. A gate here would reject writes every deployment is already making — the ontology in the store today was grown by exactly this path — so it would be switched off within a day and the drift would go back to being invisible. A report an operator reads beats a gate nobody leaves on.

Per ungoverned predicate: the IRI, how many current facts use it, how many distinct episode-written subjects carry it, and the window it has been in use.

namespace: 2 ungoverned predicate(s), 1 governed, minted by episode ingest over
2 episode-written subject(s) in urn:quipu:graph:root against 1 loaded shape(s)

UNGOVERNED http://aegis.gastown.local/ontology/rackUnit: 1 fact(s) on 1 subject(s),
in use 2026-01-01T00:00:00Z .. 2026-01-01T00:00:00Z

What counts as minted here. A predicate is reported when its subject carries prov:wasGeneratedBy pointing at a {base}episode_… activity, the predicate is in the configured base namespace, and the object is a literal. That last condition is what separates the properties map from the edge path: edge relations resolve to node references and already pass through resolve_edge_predicate, which is a fence. The two predicates episode ingest emits structurally — aegis:groupId and aegis:contentHash — are excluded by name, because the writer’s own vocabulary reported as agent drift would put a permanent floor under every report.

What “no shape mentions it” means. A predicate is treated as governed if its IRI appears anywhere in any loaded shape’s graph — as an sh:path, a target, or any other position. That is the widest reading of “mentions”, chosen deliberately: this is a report an operator acts on, and a false alarm costs more here than a missed one.

What the seen window honestly is. first_seen / last_seen are the earliest and latest valid_from among the facts using the predicate. The store keeps no separate mint timestamp, so this answers “since when has this predicate been in use”, not “when was this IRI first interned” — and a fact re-asserted with an older valid time genuinely moves first_seen backwards.

Scans the ROOT graph by default; --graph <iri> scans one named graph instead. A graph IRI that names no graph is an error, not an empty result — “no drift in the graph you named” and “there is no such graph” are different answers and only one should let an operator stop looking.

quipu audit replay <trace.jsonl>

Re-check a recorded window against the current Σ and report what promoting each rule from advise to enforce would do.

quipu audit replay ~/.local/state/hank/metrics.jsonl --db my.db
quipu audit replay trace.jsonl --json --db my.db

Exits 0 whatever it finds. Replay reports readiness, and readiness is a judgement an operator makes: failing a build because a rule has not yet fired would turn “we have not measured this” into “this is broken”, which are different states needing different responses.

Per rule, five gates — each a reason not to promote:

gatewhat it askswhy it blocks promotion
livenessdid it ever fire?a rule promoted without firing has been tested by nothing
both outcomesdid it record satisfied and unsatisfied?a one-sided check is vacuous or universal, and neither is distinguishable from broken
in specis it in Σ at all?a rule enforcing outside the specification has nothing to be promoted to
recoverabilityafter a refusal, did work on that target ever succeed?a rule nobody has got past is an outage with a reason attached
new blockshow many more actions would enforce refuse?not a gate — the number the operator is actually deciding about

Nothing is re-evaluated. The predicate needed the file as it stood and that file is gone, so this is deterministic arithmetic over records rather than a simulation.

Three limits, printed with every summary rather than kept in a footnote. It measures only traffic that happened, so a rule that would block a kind of edit nobody attempted shows zero new blocks and is not therefore safe. It counts false-positive candidates and never false positives — a block is wrong only if the action was legitimate, and no record carries that judgement. And it bounds no false negatives at all: actions a rule let through without firing look exactly like actions it correctly approved.

quipu audit replay <verdict>

Re-derive one recorded gate decision against the store as of its transaction. When the argument is not a file, it is a verdict IRI (or its local verdict_… name).

quipu audit replay verdict_3f9a… --db my.db
quipu audit replay verdict_3f9a… --delta attempt.json --json --db my.db

A refused write replays from its quarantine entry: the store is rebuilt as of the refusal in memory, the attempt applied, and the gate re-run. It re-derives when the write is refused again with the same outcome, the same rule-set digest and the same post-state digest. Under digest-only retention, present the attempt with --delta (a quipu-refused-delta/v1 JSON document); a delta whose canonical hash matches no quarantined attempt is refused as not the delta the gate judged. A verdict whose write committed replays from that write’s transaction.

Exits 1 only when a replay contradicts the record — a different outcome, rule set or post-state, or a broken seal. “Attestation only” (content purged, or digest-only with nothing presented) exits 0: it is incomplete evidence, not contrary evidence.

quipu audit quarantine

quipu audit quarantine --db my.db                          # list entries
quipu audit quarantine list --verdict <iri> --json --db my.db
quipu audit quarantine purge --graph urn:quipu:graph:root --db my.db
quipu audit quarantine purge --older-than 90 --db my.db

purge erases the sealed attempts matching --graph, --before <ts> or --older-than <days> (or --all, which must be named) and keeps each entry’s digests and seal, and the verdict facts untouched.

quipu audit tree <trace.jsonl>

Reassemble the dispatch forest from the principal chains a trace carries.

quipu audit tree trace.jsonl
quipu audit tree trace.jsonl --json

Needs no store — the tree is a property of the trace alone — and exits 0 always. A shape is not a verdict; the findings that are verdicts (a laundered chain, a partial attribution tuple) belong to quipu audit <trace>.

SARC §9.5’s attribution dilution is what this addresses: an orchestrator dispatches, a worker acts, and a flat record cannot say which link was answerable. The trace this stack emits is a sequence, so the tree here is reconstructed rather than structural, and the output says so in three places:

  • Unattributed records are not placed. A record with no chain is counted and left out. Attaching it to whichever root happened to be first would invent an answer to the question the tree exists to answer.
  • Implied dispatch nodes are flagged. A chain [orchestrator, worker] proves an orchestrator exists; it does not prove the orchestrator’s own actions are in this window. “This agent did nothing” and “this agent’s actions were not recorded” are different facts and only one is good news.
  • Collapsed nodes get a note. Two separate dispatches of the same worker by the same caller produce the same chain and land on one node. Not an error — one agent legitimately does many things — but the reader must not be told the tree is unambiguous when it is not.

quipu audit inheritance <trace.jsonl>

Check whether a constraint declared aegis:inheritedByDelegates stopped applying under delegation — SARC §9.5’s constraint laundering.

quipu audit inheritance trace.jsonl --db my.db

Exits 1 on a violation, like quipu audit <trace>.

An orchestrator is bound by a constraint, dispatches a sub-agent, and the constraint is not re-applied at the deeper layer. Nobody decided to drop it; it simply was not carried, and the record looks exactly like a constraint that legitimately did not apply.

Two findings at deliberately different severities, because one has evidence and the other has a question:

  • Violation. Constraint C decided on target T under chain [orchestrator], and a deeper action on the same target under [orchestrator, worker] did not evaluate it. C proved it could decide for T, so its absence deeper is a drop.
  • Incompleteness. C was evaluated at a dispatch node and never anywhere in its subtree. That might be laundering and might be a selector that legitimately matched nothing deeper; deciding needs the files as they stood, which quipu does not have.

Reporting both as violations would make the strong one unfindable.

aegis:onUndecidable admits only "escalate" — the same shape as aegis:onTimeout admitting only "deny". That is the decidability rescue: evaluate at the deepest layer where the constraint still decides, or hand it to a human. A constraint that silently stops applying where it cannot be checked is one an agent escapes by dispatching into a context where the evidence is absent.

A Σ where no constraint declares inheritedByDelegates reports an incompleteness, never a clean pass: “nothing is inherited” and “nothing has said whether anything is inherited” are different states.

quipu propose

Schema-change proposal queue: submit a shapes/ontology change for review rather than applying it directly.

quipu propose list --status pending --db my.db
quipu propose submit shape ex:PersonShape new-shape.ttl --proposer agent-1 --rationale "tighten cardinality"
quipu propose accept 3 --note "LGTM" --db my.db
quipu propose reject 4 --note "breaks existing data" --db my.db
SubcommandDescription
list [--status pending]List proposals
submit <kind> <target> <file.ttl> --proposer <id> [--rationale <text>] [--trigger <ref>]Queue a change
accept <id> [--note <text>]Apply and record the decision
reject <id> --note <reason>Reject with a reason

quipu policy

Policy by example: draft a placement-aimed advisory policy from an exemplar, then replay it over recorded history before anything is created. The ordering is the point — draft, backtest, read the hit list, and only then quipu knot the file, at which point the definition-time placement check still runs and can still refuse.

quipu policy draft --exemplar http://example.org/verdict/17 --name no-bare-secrets \
  --label "never commit a bare secret again" \
  --targets http://example.org/CodeEdit \
  --claim 'ASK { FILTER NOT EXISTS { $target ex:containsSecret true } }' \
  --out draft.ttl
quipu policy backtest draft.ttl --last-txs 500 --db my.db
quipu knot draft.ttl --db my.db
SubcommandDescription
draft --exemplar <iri> --name <slug> --label <sentence> --targets <type-iri> --claim <ask>Emit advisory Turtle for one policy. Never writes to the store
backtest <candidate.ttl>Replay the candidate over the store’s transaction log

draft flags:

FlagDescription
--exemplar <iri>The Verdict / DecisionRequest / edit record that motivated the rule (required)
--name <slug>Local name for the policy IRI; sanitised to [A-Za-z0-9_-] (required)
--label <sentence>The intent sentence, kept verbatim as rdfs:label (required)
--targets <type-iri>Target entity type, aegis:targets (required)
--claim <ask>The compliant condition: a SPARQL ASK over $target (required)
--class soft|hardaegis:constraintClass (default: soft)
--point <point>aegis:verificationPoint (default: derived from the class — soft→PAA, hard→PAG)
--layer <layer>aegis:hostedAtLayer (default: tool)
--authority <who>aegis:authority on the parent Directive
--out <file.ttl>Write the Turtle to a file instead of stdout

A drafted policy is born advisory — aegis:effect "warn" is a constant, not a flag. Promotion to enforcement goes through the existing advisory→enforcing gates over recorded traffic.

backtest flags:

FlagDescription
--last-txs <N>Window the replay to the last N transactions (default: the whole log)
--from-tx <A> --to-tx <B>Explicit transaction window; both must be given together

Output is one line per hit (tx <id> (<timestamp>): would have fired on <target>) followed by a summary. The summary distinguishes “0 hits” from “cannot evaluate”, and the command exits 1 when nothing could be measured so a script that knots on success cannot read an unevaluable candidate as clean.

quipu gate shadow

Judge a candidate policy set over recorded history before any of it is created, and report which committed writes it would refuse that the governing set admitted (and the reverse). Never writes.

sqlite3 live.db ".backup copy.db"      # a quiescent copy, never the live store
quipu gate shadow --rules candidate.ttl --db copy.db --since 7d
quipu gate shadow --rules candidate.ttl --db copy.db --from-tx 1000 --to-tx 2000 --json
FlagDescription
--rules <file.ttl>Candidate policies: action-boundary aegis:Policy nodes with aegis:targets and aegis:claim (required)
--db <copy.db>A quiescent copy of the store (required; see refusals below)
--since <dur>Window: transactions stamped within 90m, 24h, 7d, …
--from-tx <A> --to-tx <B>Explicit window; both together
--last-txs <N>The last N transactions (default: the whole log)
--max-txs <N>Stop after judging N; the report says where it stopped
--mode add|replaceadd (default) layers the candidate over the governing set, same IRI replacing; replace makes the candidate the whole set
--jsonMachine-readable report

Each transaction is judged by the write gate’s own evaluator against the post-state it saw, with the router read as of that transaction’s time. The baseline is the policy set in force at that transaction, so a policy change inside the window is reported as a boundary and never attributed to the candidate.

Refusals, not advice. The command refuses the configured live store and any path with a -wal, -shm or -journal sidecar (an open connection, or a copy taken mid-write): it reads with SQLite immutable=1, which ignores locks and the WAL and could see torn state on a live file.

Limits it reports rather than hides: refused writes are rolled back and cannot be replayed, so they are counted, not judged; recorded verdicts are joined by aegis:gatedTx when present and otherwise by the next-transaction convention, with the match rate printed (and UNMEASURED when nothing joined); and a baseline that would refuse a committed write is counted as the fidelity signal (enforcement off then, an approval, or a reconstruction gap).

quipu path

Golden-path analysis over recorded trajectories: the provenance cone, the backtest, and a grammar draft. All three are reads; draft prints Turtle for a human to review and load. See the golden paths design.

quipu path cone http://example.org/traj/42 --via http://example.org/derivedFrom --hops 6 --db my.db
quipu path backtest http://example.org/traj/42 --omit http://example.org/step/3 --json --db my.db
quipu path draft http://example.org/traj/42 --name fast-review --label "the short path" \
  --via http://example.org/derivedFrom \
  --omit http://example.org/step/3 --by http://example.org/decision/9 --db my.db

The trajectory IRI is the first positional argument to every subcommand.

SubcommandDescription
cone <trajectory-IRI>Which steps did the falsifier-gated verified result depend on?
backtest <trajectory-IRI>Replay a pruned candidate over past trajectories sharing a work-item topic
draft <trajectory-IRI>Emit gp-grammar/1 Turtle for the blessed path
FlagSubcommandsDescription
--via <predicate-IRI>cone, draftDerivation predicate to walk, in addition to verifiedBy (always followed). Repeatable
--hops <N>coneDepth bound for the derivation walk (default: 8)
--omit <step-IRI>backtest, draftStep the candidate omits. Repeatable
--by <decision-IRI>draftThe human Decision authorising the paired --omit. Repeatable
--dead-end <step-IRI>draftMark a step a dead end in the drafted grammar. Repeatable
--name <local-name>draftLocal name for the drafted grammar (required)
--label <text>draftHuman label for the drafted grammar (required)
--jsoncone, backtestEmit the report as JSON instead of the text table

cone verdicts are IN-CONE (load-bearing; pruning needs a human Decision), OUT-OF-CONE (mechanically prunable) or CANNOT-EVALUATE (no derivation edges recorded — never silently prunable). draft refuses when the count of --omit flags does not match the count of --by flags: a human cut without its Decision is a silent edit of history.

quipu ontology

Manage stored OWL ontologies (versioned: re-loading a name closes the prior version). Requires the owl feature.

quipu ontology load my-domain domain.ttl --db my.db
quipu ontology list --db my.db
quipu ontology remove my-domain --db my.db

quipu doctor labels

Diagnose graph-label state: which graphs carry freshness/trust/policy labels and which are undeclared.

quipu doctor labels --db my.db

quipu pack / quipu unpack

Knowledge packs: export one named graph as a self-describing, attachable .pendant.db artifact (facts, manifest, shapes, stored queries, optionally vectors), verify one, or import one into a local graph.

quipu pack urn:example:graph --out domain.pendant.db --name "domain" --version 1.0.0
quipu pack urn:example:graph --out domain.pendant.db --shapes s.ttl --queries q.json --with-vectors
quipu pack urn:example:graph --out domain.pendant.db --space 7
quipu pack --verify domain.pendant.db
quipu pack urn:example:repo --out repo.pendant.db --repo scbrown/example --repo-sha "$BASE_SHA" --model-id all-MiniLM-L6-v2 --model-version 1
quipu unpack repo.pendant.db --expect-repo scbrown/example --head-sha "$(git rev-parse HEAD)" --into urn:local:domain --db my.db
FlagDescription
--out <file>Output pack path (required for pack)
--name / --versionManifest metadata
--space <N>Ship the pack in term space N so it attaches to a consumer without id collisions (same machinery as quipu db respace; the content hash is unchanged — a space moves ids, not content). Not applicable to --format turtle
--shapes <S> / --queries <Q>Ship shape sets / stored queries (repeatable)
--with-vectorsInclude embeddings (refused unless the SQLite vector backend is active)
--format turtleAlso embed a Turtle serialization
--fullA LOSSLESS whole-store pack for internal backup, read by quipu restore. Carries every carried table with its full history, so it refuses an outward destination
--full --format textThe same whole-store pack as text — a git-friendly directory rather than a SQLite file. Does not transport derived data (vectors); the manifest carries the recipe to rebuild it
--verify <file>Recompute and check the pack’s content hash
--into <graph-iri>Unpack target graph (default: the pack’s own graph IRI)
--repo / --repo-sha / --model-id / --model-versionAll-or-none provenance for a repository pack. The manifest also carries the Quipu version, build SHA, and pack schema version.
--expect-repo / --head-shaVerify repository identity while loading and report the incremental ingestion range from the pack SHA to checkout HEAD. Exact pack content already loaded returns unchanged without duplicate facts.

Repository packs should be attached to a GitHub release, not committed to the repository, once they exceed 10 MiB. A loader must download to a temporary path, run quipu pack --verify, and delete a failed download before opening the destination. quipu unpack repeats verification before every load. After a successful load, ingest repository changes over repository_sha..head_sha; future setup runs are incremental because the destination records the verified content hash and returns unchanged for the same asset.

quipu pack --full --format text

A lossless whole-store pack rendered as text. --full on its own is lossless but binary (a VACUUM INTO copy); quipu share is text but carries only current facts. This is the artifact that is both: git-friendly and reconstructing.

quipu pack --full --format text --destination internal --out store-pack/ --db my.db
quipu restore store-pack/ --db restored.db

It writes a directory, not a file:

store-pack/
  manifest.json     what the pack claims, including its content hash
  schema.sql        DDL for every object, applied before any row
  data/<table>.sql  canonical INSERTs, one file per table

Rows are emitted as one INSERT each, ordered by the row text itself. That ordering is what makes the format git-friendly: a row moving on disk produces no diff, so a pack committed to a repository only changes when its contents do.

restore accepts either form. For a text pack it rebuilds the store, checks referential integrity, and then refuses unless the reconstruction hashes identically to what the manifest claims — nothing is written to the destination until that holds. A dump missing a file, a table, or a single row is rejected rather than installed as a quietly smaller store.

A text pack does not carry derived data (vectors), because hex-encoded embeddings would make it gigabytes. It records the row count and the embedding recipe instead, and restore prints a REGENERATE: line so an incomplete store cannot be mistaken for a complete one. At pack time, omitted vector rows with a missing model name or SHA-256 digest make packing refuse with exit code 1 before writing the destination. To explicitly accept a backup without reproducible vectors, pass --allow-missing-embedding-recipe; this succeeds with a WARNING: on stderr and preserves the incomplete recipe honestly. Set [quipu.embedding] model_path to the original readable model file and repack to record its name, digest, and configured dimension. A recorded recipe identifies the configured model; it does not verify that this model produced the source vectors. An empty vector table needs no warning. Binary pack --full retains vectors and does not require a regeneration recipe.

Like --full, this refuses an outward destination: it carries the event log and every operational table, and publishing one is the operator’s decision.

quipu graph

The graph-registry commands: offline import, and the deep-freeze lifecycle (see Graph Kinds & Deep Freeze).

quipu graph import other.db --as urn:app:imported --db my.db
quipu graph freeze urn:app:runs/2026-07 --out /var/quipu/archive --db my.db
quipu graph thaw urn:app:runs/2026-07 --db my.db
quipu graph list --kind operational --db my.db
quipu graph list --frozen --db my.db

freeze exports the graph’s full history to a .pendant.db archive, verifies it by content hash, deletes the local rows and re-attaches the pack read-only; the graph stays queryable at the same IRI and refuses writes until thaw. list prints iri class kind lifecycle source per graph.

quipu import

Stage a git-native share directory without touching ROOT:

quipu import ./share --source https://example.org/alice --db my.db
quipu import promote sha256:0123... --actor reviewer --db my.db

The first command reads manifest.json, export.nt, and shapes.ttl, verifies their hashes, resolves exact local identities, surfaces fuzzy review candidates, and quarantines facts that fail local vocabulary or SHACL checks. The second is the separate, explicit ROOT-admission step and only accepts eligible staged shares. Both print the same JSON fields as the REST endpoints.

quipu fork

Persistent named forks (quipu-gp5): fork ROOT as of any transaction into an independent committed-class named graph (urn:quipu:fork:<name>), read it exactly like the main line, diff it, then drop it or promote it. Promotion re-enters through the SHACL + policy write gates — a refused promotion writes nothing and the fork stays open. Fork ergonomics are never a gate bypass.

quipu fork 42 --name experiment --db my.db     # fork ROOT as of tx 42
quipu fork list --db my.db
quipu read "SELECT ?s ?p ?o WHERE { ?s ?p ?o }" --fork experiment --db my.db
quipu fork diff main experiment --db my.db     # each side: a fork name, or 'main'
quipu fork promote experiment --db my.db       # delta re-enters via the gates
quipu fork drop experiment --db my.db          # terminal; the name is not reusable
SubcommandDescription
<tx> [--name <n>]Create: materialize ROOT-as-of-<tx> into a new fork (default name fork-<tx>)
listName, fork-tx, status, created-at for every fork
diff <a> <b>Present-state triple diff between two forks (or main)
promote <name>Apply the fork’s delta to ROOT through the write gates; SHACL refusal leaves ROOT untouched
drop <name>Close the fork; its facts remain as history, the name is not reusable

Reads: --fork <name> on quipu read, or the fork field on POST /query / quipu_query. Unknown and dropped forks are refused loudly — never a silent fall-through to ROOT.

quipu db attach --list

List the databases mounted alongside this store — the [[quipu.attachments]] layers (see Configuration) and deep freeze’s archives, which no config declares.

quipu db attach --list --db my.db

Output is alias, path, and mount mode (always ro), tab-separated. A declared layer that could not be mounted refuses the open instead of appearing here, so everything listed is genuinely composed.

quipu db respace

Move a store into a fresh term space so it can be attached to another store without id collisions. Reads the source read-only; writes a new file.

quipu db respace --into 7 --out respace.db --db my.db

quipu events refusals

Count refused writes by gate (shacl | policy | authority | owl | placement) — the incident-rate denominator. Reads the write.refused events the write gates record; the raw events are served by GET /events?types=write.refused. See the REST API reference for what a refusal event records (metadata only, never the refused bodies) and the speculate exclusion.

quipu events refusals --db my.db

quipu graph import <db>

Import another quipu database’s ROOT graph as a named graph in this store.

quipu graph import other.db --as urn:import:other --db my.db

quipu migrate-vectors

Migrate stored embeddings between vector backends (requires the lancedb feature).

quipu migrate-vectors --from sqlite --to lancedb --dry-run --db my.db

quipu hook session-capture and quipu hooks

quipu ships one agent hook: a Stop hook that asks the agent, once per session, whether the session produced durable knowledge worth writing as an episode. The agent may act or reply skip; the hook never blocks a second stop.

quipu hooks bundle                      # print the hook bundle (schema st.hook-bundle/1)
quipu hooks install --harness claude    # merge into ~/.claude/settings.json
quipu hooks install --harness codex     # merge into $CODEX_HOME/config.toml (default ~/.codex)
quipu hooks status                      # exit 1 unless every harness has the hook
quipu hooks uninstall                   # remove only quipu's hook

With no --harness, both harnesses are written. --project writes ./.claude/settings.json or ./.codex/config.toml instead. Install is idempotent, leaves every other hook in place, and keeps the previous file as <file>.bak-quipu. When a shantytown registry answers (st ops hooks list), install and uninstall register the bundle with st instead, which renders it into every role’s settings; pass --no-st to write the harness config directly.

The hook itself, quipu hook session-capture, reads the Stop-hook JSON on stdin and prints at most one response. It always exits 0, and every failure is silence rather than a malformed response. Its guards, in order:

  1. Scope. The crew comes from GT_CREW, else from a .../crew/<name>/... working directory. An unidentified crew is never interrupted. QUIPU_HOOK_CREWS (space-separated, default *) narrows the scope.
  2. Loop guard. stop_hook_active: true means the hook already fired this turn, so it stays silent.
  3. Once per session. A solicited-<session_id> marker in the state directory. Markers older than two days are reaped.
  4. Durable denominator. Each solicitation appends {ts, session_id, crew} to solicit-log.jsonl before it is sent. If that append fails the hook stays silent, so the log can never undercount.
VariableDefaultMeaning
QUIPU_SERVERhttp://127.0.0.1:3030Server URL named in the message
QUIPU_HOOK_GROUPdefaultEpisode group_id named in the message
QUIPU_HOOK_CREWS*Crews the hook may interrupt
QUIPU_HOOK_STATE_DIR~/.quipu-hookMarkers and solicit-log.jsonl
GT_CREW(unset)The crew name, when the cwd does not carry it

The message prefers authenticated Quipu MCP tools or the deployment’s gated graph-extract helper. For HTTP fallback, /search stays open, but /shapes vocabulary reads require a bearer just like /episode and /propose. The agent resolves a non-empty QUIPU_AUTH_TOKEN, then QUIPU_AUTH_TOKEN_FILE or the configured client token file. A missing credential stops the request before HTTP; it is not a reason to send an unauthenticated probe. Credentials stay server-side where possible and must never be printed. Requests carry X-Quipu-Client: session-capture. The act rate is episodes whose source carries the session id / lines in solicit-log.jsonl.

Create and label named graphs

quipu graph create urn:example:jobs --actor urn:example:agent --db tutorial.db
quipu graph label urn:example:jobs --label '{"kind":"operational","freshness":"fresh"}' --db tutorial.db
quipu graph list --json --db tutorial.db

Both verbs call the same handlers as MCP/REST. --timestamp <RFC3339> pins the creation/declaration time; otherwise current UTC is used. --actor is declared provenance, not authorization. Create is idempotent and preserves the original provenance. Label fields match quipu_graph_label, including trust, policy, durability and validity bounds. JSON listing exposes creation and all labels; the existing tab-separated listing remains the default.

graph overlay-create, graph overlay-write, and graph overlay-compose accept --input '<JSON object>' using the corresponding MCP tool input fields. For the complete new-class write recipe, see named graphs through MCP.