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 two lines without loading configuration or opening a database:

quipu <version>
git_sha: <build-commit>

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. 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 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 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 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 the file nor the parser. And 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.

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 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 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 .qpack.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.qpack.db --name "domain" --version 1.0.0
quipu pack urn:example:graph --out domain.qpack.db --shapes s.ttl --queries q.json --with-vectors
quipu pack urn:example:graph --out domain.qpack.db --space 7
quipu pack --verify domain.qpack.db
quipu pack urn:example:repo --out repo.qpack.db --repo scbrown/example --repo-sha "$BASE_SHA" --model-id all-MiniLM-L6-v2 --model-version 1
quipu unpack repo.qpack.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 .qpack.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