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

The Quipu stack — shared knowledge with rules and history

The Quipu stack is built around a governed, bitemporal knowledge store: shared facts, decisions and work history your agents can query. Quipu can check writes against your rules, records where each fact came from, and answers what was true when. Bitemporal means keeping both when a fact was recorded and, when supplied, when it was true.

Caboodle installs and configures this stack, then proves the selected tools work. Quipu holds the knowledge. Camayoc prepares knowledge and vocabulary for it. Bobbin searches code and can include knowledge from Quipu; Yupana shows code impact and can read policies from Quipu. Desire Path turns agent mistakes into feature requests and also works on its own. Seeds tracks work items in the Quipu knowledge base. You can use the tools separately or connect them around the same store.

Write refusal is configurable, not automatic in every installation: enable Quipu’s write checks for the rules you want enforced. See Quipu’s configuration reference for the settings and their defaults.

Three ways in

1. Try it in your browser

Open Quipu Explore — no installation. The real store runs as WebAssembly in your browser. Use the SPARQL box, edit a fact, and export a knowledge pack.

2. Start in Claude Code

Add the Camayoc plugin, then run its bootstrap:

/plugin marketplace add scbrown/camayoc
/plugin install camayoc@camayoc
/camayoc:bootstrap

Camayoc brings its own Quipu, loads the ontology, and proves the write gate with probes that must be refused. See the Camayoc setup guide.

3. Install the whole kit

Install Caboodle, then start its guided interview:

caboodle init --guided

Review the generated plan before applying it. The getting-started guide walks through installation, verification, and recovery; profiles let you choose how much of the stack to install.

Around the stack

Explore Shuttle, Creel, Shanty, Desire Path for turning hallucinations into feature requests, and Yupana for policy enforcement and code impact. Seeds tracks work items in the Quipu knowledge base. Agents can run under Shantytown or Creel.

Work-tracking dependencies include the beads_rust fork, based on Dicklesworthstone/beads_rust. See the stack for the component and dependency list.

Getting started

Install Caboodle with the release installer, then try the smallest profile: Quipu and Camayoc. Start in an empty directory.

curl -fsSLO https://raw.githubusercontent.com/scbrown/caboodle/main/examples/caboodle-intent.toml
caboodle plan --profile kg --intent caboodle-intent.toml
caboodle install

The plan command prints plan: caboodle-plan.toml. Read that file before installing: it selects Quipu and Camayoc, and retains the example intent. Installation prints progress on stderr and these lines on stdout:

quipu: applied
camayoc: applied
stack configuration: applied
quipu: verified
camayoc: verified

Each verified line means an isolated functional round trip passed, including a control proving the marker was absent before the write. The example intent is a starting contract, not a seeded production graph; see question verification.

Guided interview

Use caboodle init --guided instead of the first two commands to describe your own use interactively. Run caboodle doctor before installing to see platform or prerequisite blockers.

promptwhat to type
profileretrieval for a first install. See profiles.
what should this installation help you do?One plain sentence. It is recorded in the plan.
how many themed crew members0 unless you run a named team of agents.
how many ontology questionsPress enter on a first install: caboodle uses a built-in self-test question that checks the store answers a query, and asks nothing else. Or type a number to write your own; each question is a check the finished graph must pass.
the question’s five fieldsOnly if you typed a number. A question in words, its answer shape, the seed fact that answers it, a SPARQL SELECT or ASK query, and a word the answer must contain.

Recovery

  • caboodle doctor first. It names the blocker and the fix.
  • <tool> install step ... no checksummed CABOODLE release for <platform>: build that tool with the cargo install command doctor prints, then rerun caboodle install. Caboodle adopts an installed tool that passes its checks.
  • <tool> functional verification: the tool installed but its round trip failed. The error includes the tool’s own output. Fix it and rerun caboodle install; tools that already passed are not redone.
  • macOS with Bobbin before v0.18.0 (the pin CABOODLE carried before this release was v0.16.2): bobbin panics with Failed to load ONNX Runtime dylib: bobbin looks for its bundled runtime next to the ~/.cargo/bin/bobbin symlink rather than next to the real binary. For that release, run export ORT_DYLIB_PATH="$HOME/.local/share/caboodle/bobbin/v0.16.2/lib/libonnxruntime.dylib" (add it to your shell profile) and rerun caboodle install.
  • A new build still behaves like the old one: another copy earlier on PATH wins. Doctor lists every copy in the order your shell finds them, and caboodle verify fails with SHADOWED when the copy PATH runs reports a different version from the one caboodle installed. Remove the stale copy or move ~/.cargo/bin earlier on PATH.
  • no plan at caboodle-plan.toml: you are in a different directory from the one where you ran caboodle init --guided.

Do not delete .caboodle/ to recover. It holds your interview answers and the last proven state.

Reproduce the README proof

On Linux x86_64, from a checkout, run scripts/test-readme.sh. It extracts the README’s shell blocks, runs the checksummed release installer and the three first-success commands under env -i with a fresh home, then diffs stdout against the README’s own expected-output block. It does not reuse host binaries for the stack. The Documentation workflow runs this proof on every push and PR.

For local link checks, install mdBook 0.5.4 and lychee 0.24.2, then run just docs-check. pre-commit install enables the same check before commits. The check builds the book under its Pages base path and checks files, anchors and public URLs. The source-build route additionally requires Rust; agent registration additionally requires the selected client and profile.

Installing and removing Tambo

Tambo is the renamed Caboodle installer. For the transition release, both tambo and caboodle are available. Existing caboodle-plan.toml and .caboodle state paths remain compatible; no install-state move is required. The canonical archive is tambo-vVERSION-TARGET.tar.gz; a compatibility archive named caboodle-vVERSION-TARGET.tar.gz carries the same two commands for legacy self-update clients. Both have filename-bound SHA-256 checksum files.

The planned managed Linux and macOS cutover uses apt and Homebrew to update both commands as a package after the first official Tambo release. The Homebrew formula rename will migrate caboodle to tambo; the Linux transitional caboodle package will depend on tambo. The checksummed release installer remains the standalone fallback. A standalone self-update of a dual-command installation verifies and updates both commands together, restoring both prior files and state if either installed read-back fails.

Caboodle publishes a release only when a vVERSION tag exactly matches the version in Cargo.toml. Each supported archive has a sibling .sha256 file; the installer verifies that checksum before replacing the binary.

Caboodle’s built-in HTTPS asset downloads make at most three attempts. Server errors (HTTP 5xx) and transient resolution, connection, timeout, partial-transfer, send/receive or HTTP/2 stream errors are retried after one and then two seconds. Each attempt has a 10-second connection timeout and a 120-second transfer limit. Retry logs identify the URL, attempt, curl status, HTTP status and delay; recovery logs name the successful attempt. Failed attempts use disposable files. HTTP 4xx (including missing assets), certificate and local file errors fail immediately. Checksum verification happens after downloading and is never retried or bypassed. This policy covers downloads made by the Caboodle binary, not the bootstrap shell installer above it or external package managers.

Homebrew member updates

For a Mac plan with stack formulas, tambo update refreshes Homebrew metadata once before checking members. An unpinned formula follows the tap’s current stable version, even when the installed version is already ahead of Tambo’s embedded minimum release. A newer installed version is never downgraded. brew pin scbrown/tap/bobbin holds that formula; the updater reports the hold and never unpins it. Use brew unpin scbrown/tap/bobbin when you intend to resume stable updates.

A failed metadata refresh, unreadable channel, failed upgrade, or version read-back mismatch is an error. After an upgrade, the formula receipt must reach stable and agree with the executable, and functional verification must pass. The release fallback still requires the embedded reviewed minimum when no stack formula is available. check-updates continues to report drift from those embedded release pins; it does not refresh package metadata.

Release install

Run the checksummed release installer in one line:

curl -fsSL https://raw.githubusercontent.com/scbrown/caboodle/main/scripts/install.sh | bash

Pin a release by setting the version on the receiving shell:

curl -fsSL https://raw.githubusercontent.com/scbrown/caboodle/main/scripts/install.sh | CABOODLE_VERSION=vX.Y.Z bash

The script still verifies the release archive’s SHA256 checksum before replacing the binary. Read the installer source when inspecting the bootstrap. After installation, caboodle --version reads back the installed release.

The compatibility repository URL remains the bootstrap default through the rename. The installer prefers a tambo- archive with both commands. Only an HTTP 404 selects the historical caboodle- archive, which may contain just caboodle; pinned releases such as v0.2.23 therefore remain installable. Checksum, server and transport failures stop installation without trying an alternative archive.

Supported release targets are Linux x86_64, macOS x86_64, and macOS arm64. The default destination is ${CARGO_HOME:-$HOME/.cargo}/bin/caboodle; override it with CABOODLE_INSTALL_DIR when that directory is managed elsewhere.

caboodle --version prints the release version and the commit it was built from, for example caboodle 0.2.3 (1a2b3c4d5e6f). Two builds of one release can then be told apart. The installer warns when a different caboodle comes earlier on PATH than the one it just installed, and caboodle doctor warns when PATH runs a different build from the one you invoked. Remove the stale copy or reorder PATH.

Source install

A Rust toolchain can install the reviewed revision directly:

cargo install --git https://github.com/scbrown/caboodle \
  --rev <reviewed-commit-sha> --locked
caboodle --version

Preflight

caboodle doctor reports what would stop an install on this host and changes nothing. With a plan in the current directory it checks that plan; without one it checks every tool in the everything profile. It exits nonzero when any line is a FAIL:

  • the install directory ($CARGO_HOME/bin, default ~/.cargo/bin) is not on PATH, so installed tools could not be read back;
  • a command an install or verification step shells out to is missing (curl, tar, git, bash, python3, sha256sum, go for Desire Path, cargo for the lancedb Quipu flavor);
  • a selected tool is not installed and has no reviewed release for this platform (for example, Yupana on Linux arm64), with the source build to run instead.

It warns, without failing, when a tool differs from the reviewed version, when several copies of a tool are on PATH (the first one runs), and when a Quipu server is already live at QUIPU_SERVER. Camayoc verification uses its own temporary server and never loads the ontology into that existing server.

First proof and resume

The interview writes a plan and changes nothing else. Read the complete file before applying it:

caboodle init --guided
less caboodle-plan.toml
caboodle install
caboodle verify-questions

If input or an install is interrupted, rerun the same command. Interview answers resume from .caboodle/interview.toml; install state resumes from .caboodle/state.json. A green result means version read-back and functional reader-path checks passed, not merely that an installer exited zero.

If a tool cannot install or pass version read-back, apply and install continue attempting the remaining tools, save each success, and exit nonzero with all tool failures. Any saved proof for a failed tool is cleared. Stack configuration, model provisioning, crew setup, share imports and functional verification wait until every selected tool applies successfully. Fix the reported gaps, then rerun the same command; already-current tools are reused. State-write and evidence-queue failures still stop immediately.

Two plan-level choices extend what the box installs, both documented with their proofs in Profiles: --quipu-flavor lancedb builds the reviewed Quipu revision with the lancedb feature compiled in (proven by the server’s GET /version per-feature compile map), and --embedding-model <spec.toml> provisions checksum-pinned embedding-model artifacts (a mismatched download is deleted and fails the step; verify re-hashes the artifacts on disk).

Registering MCP servers

Caboodle owns binary installation, version convergence, and functional proof. Shantytown owns harness registration: it projects the MCP/skill manifest from Quipu into the workspaces named by the rig’s crew cards. Installing binaries alone does not register servers.

After a green install, on a configured Shantytown rig:

caboodle project-settings --root /path/to/.shanty
# Optional: select one crew member or read identities directly from Quipu.
caboodle project-settings --root /path/to/.shanty --agent ada --registry quipu

This delegates to st --root /path/to/.shanty ops provision --json. It reads the rig’s existing crew, even when the installation plan has no crew profile, and requires the configured Quipu tooling manifest (SHANTY_TOOLING_MANIFEST). Server names, transports, and secret references come from that manifest; Caboodle does not maintain another server list. A rig whose manifest names yupana, bobbin, forgejo, and homelab registers all four. A missing crew reports “no crew on this rig yet - run st fleet init / st agent new first”. Missing manifest, credentials, or failed read-back refuses registration. This needs a Shantytown version exposing st ops provision; upgrade st if it rejects the command. No agents are launched or restarted. Existing sessions need their next normal start to load the new registration.

--policy-only retains the old plan-based policy-summary export to caboodle-settings/ (--plan and --output customize it). Those summaries do not register MCP servers. Normal registration does not read the install plan.

For a standalone harness, or until the adapter is available, register explicitly. For Claude Code, substitute your deployment’s reviewed HTTP MCP endpoints:

claude mcp add bobbin -- "$(command -v bobbin)" serve "$PWD"
claude mcp add yupana -- "$(command -v yupana)" serve
claude mcp add --transport http forgejo https://forgejo-mcp.example.com/mcp
claude mcp add --transport http homelab https://homelab-mcp.example.com/mcp
claude mcp list

Homelab MCP exposes the quipu_* tools; it is a separately operated proxy, not installed by Caboodle. Use your deployment’s authentication procedure without putting credentials in the plan. Bobbin’s knowledge tools are not a replacement for Homelab’s Quipu tools. HTTP endpoints above are placeholders, not public services. For Codex, use the rig adapter to emit its harness-specific settings.

Claude Code cloud environments

scripts/setup-environment.sh is the version-controlled copy of the setup script for a Claude Code cloud environment. Paste its body into the Setup script field (claude.ai/code → the cloud icon above the message box → Add/edit environment) — the field takes a script, not a path, and it runs before the repo is available, which is why the file exists to be pasted from rather than executed.

It bootstraps quipu (checksummed prebuilt quipu and quipu-server release binaries), caboodle itself (prebuilt release binary via scripts/install.sh), the shared tooling the stack’s quality gates need (just, bd, pre-commit, mdbook, mdbook-mermaid, cffi), and stages the stack knowledge packs into ~/.caboodle/packs/, verified with quipu pack --verify — a pack that fails verification is deleted rather than trusted. It deliberately does not build the rest of the corpus: that is caboodle’s own job, and a session that needs the full stack runs caboodle install against a reviewed plan so every tool is proved rather than assumed.

Set CABOODLE_ALLOW_SOURCE_FALLBACK=1 only when the Quipu release artifact is unavailable and the environment has enough time and memory for a Rust build. The normal setup path never compiles Quipu from source.

Owned Quipu installs provide qp as a relative link to quipu. Both command names execute the same CLI; quipu remains supported. Apply repairs a missing alias on a current pair without reinstalling or downgrading that pair. Existing conflicting qp files in the destination or on PATH cause refusal. Verification checks the link, executable identity and matching version output without creating files. --skip-install requires the alias to be present already.

An externally owned Quipu must provide its own command alias; Caboodle verifies it but does not create it. A paired release update retains the alias across a CLI replacement or rollback because the link follows the canonical quipu path.

Hosts that only ever run update-self (such as a scheduled installer consumer) never run apply, so update-self also heals the alias. After a verified swap it runs the newly installed binary once to repair the owned directory ($CARGO_HOME/bin, default ~/.cargo/bin). That pass creates a missing qp, or re-points a qp symlink with the wrong or a dangling target. A regular file or directory named qp is foreign: it is reported and left in place. A failed repair is reported but never fails the update. Because the old binary performs the swap, the first release carrying this behaviour repairs on the update after it.

Stack knowledge packs

packs/ carries quipu knowledge packs for working with the stack:

  • stack-map/pendant/ — what each tool is, where it lives, and how the pieces relate.
  • stack-operations/pendant/ — how each repo builds, tests, and proves itself, and the git discipline that binds them.

A pendant is the artifact once called a qpack (named for the khipu’s pendant cords, each a self-contained record hanging from the primary cord). The old Each directory contains manifest.json, manifest.ttl, canonical export.nt and shapes.ttl. SQLite databases are scratch build inputs, not repository artifacts. The former .pendant.db and .qpack.db paths remain symlinks to these directories, so directory-based imports can retain their old paths. They are directory aliases, not binary SQLite packs. Use quipu import, not quipu unpack or quipu pack --verify, for these new artifacts. Previously published binary packs retain their original binary consumer commands. Alias removal is separate from this conversion and is not scheduled before the release after the announced 0.14 deprecation.

quipu import packs/stack-map/pendant --db scratch.db
quipu import packs/stack-operations/pendant --db scratch.db

Import verifies the bundle’s hashes and stages or quarantines its graph; it does not promote it automatically. These shares intentionally contain no domain shapes or stored queries. An empty receiver quarantines stack-map because https://caboodle.dev/ns#StackComponent is outside its local vocabulary. Configure reviewed receiver shapes for that class before expecting admission. Hash integrity alone is not vocabulary, shape or producer certification.

The Turtle sources in packs/src/ remain the review surface. scripts/build-stack-packs.sh rebuilds and verifies both bundles before replacing output; a failed build or native verification preserves existing bundles. Rebuild by hand when sources change: creation time and source identity can change manifests without changing the semantic graph. Python 3 and Quipu with text-share support are required. PACKS_OUTPUT_DIR selects an isolated output root for experiments; python3 scripts/test-stack-pendants.py exercises native integrity, quarantine, tamper refusal and failure preservation.

Shipping a pack from a repository

Each repository can publish its graph beside its release binaries as a Quipu share. A .pendant.tar.gz release asset is a deterministic archive of the same text bundle, not a SQLite database:

quipu share --graph https://example.org/knowledge/repository/example \
  --db .bobbin/quipu/quipu.db --out repository-share
tar --sort=name --mtime=@0 --owner=0 --group=0 --numeric-owner \
  -czf repository.pendant.tar.gz -C repository-share .

Publish the archive as an immutable release asset. A clone points Quipu at the asset; Quipu fetches bounded bytes, verifies the manifest and payload, and opens a fresh in-memory store without a local download step:

quipu import \
  https://github.com/scbrown/quipu/releases/download/quipu-ai-v0.11.1/quipu-quipu-ai-v0.11.1-repository.qpack.tar.gz

Releases still publish the asset as *-repository.qpack.tar.gz (checked on quipu-ai-v0.11.1). The pendant rename has not shipped yet; when it does, the asset becomes *-repository.pendant.tar.gz.

For a modified graph, quipu share --since <parent-share> --out <delta-dir> writes a parent-bound SPARQL 1.1 Update delta. Import rejects a wrong parent, changed update bytes, unrestricted operations, or a mismatched result digest. Bobbin’s .bbpack index remains a separate binary release artifact.

Uninstall

Remove only the Caboodle binary and optional local Caboodle state. Installed corpus tools are intentionally left in place because they may be shared with other workflows:

rm "${CARGO_HOME:-$HOME/.cargo}/bin/caboodle"
# Optional, from the directory where Caboodle was run:
rm -r .caboodle

Reviewed-pin drift

caboodle check-updates proposes an update only when the installed version is older than the reviewed pin. A newer version is reported as ahead of the pin; an unparseable or same-version identity mismatch is reported as version drift. These reports remain nonzero because the installation differs from the review. caboodle update refuses a reviewed-pin downgrade before replacing the tool. There is no --allow-downgrade escape: a deliberate rollback requires a separately reviewed installation, rather than a normal stack update.

Replacing a cargo-installed Bobbin

If apply refuses to replace an existing Bobbin executable, it preserves your installation. To switch a regular-file Cargo install to the reviewed release, first stop any automatic updates and choose a backup that does not already exist. The following works on macOS and Linux and uses $CARGO_HOME/bin when configured. It creates a hard-link backup beside the executable, retaining its bytes and permissions, before removing the original directory entry. It refuses symlinks, directories, and an existing backup (including a dangling symlink). It does not change Cargo’s install metadata or uninstall the crate.

python3 - <<'PYTHON'
import os
from pathlib import Path
import stat

binary = Path(os.environ.get("CARGO_HOME", str(Path.home() / ".cargo"))) / "bin/bobbin"
backup = binary.with_name("bobbin.before-tambo")
if not stat.S_ISREG(binary.lstat().st_mode):
    raise SystemExit(f"Expected a regular file; preserved {binary}")
os.link(binary, backup, follow_symlinks=False)  # fails if backup already exists
binary.unlink()
print(f"Saved {backup}; rerun apply with your existing plan and state")
PYTHON
caboodle apply --plan caboodle-plan.toml --state .caboodle/state.json
caboodle verify --plan caboodle-plan.toml --state .caboodle/state.json

Keep bobbin.before-tambo until verification succeeds. If either command fails, stop and inspect the failure. To restore, remove only the managed Bobbin symlink created by this attempt, if present, then run the following. It refuses an occupied destination and leaves the backup in place:

python3 - <<'PYTHON'
import os
from pathlib import Path
import stat

binary = Path(os.environ.get("CARGO_HOME", str(Path.home() / ".cargo"))) / "bin/bobbin"
backup = binary.with_name("bobbin.before-tambo")
if not stat.S_ISREG(backup.lstat().st_mode):
    raise SystemExit(f"Expected a regular backup; preserved {backup}")
os.link(backup, binary, follow_symlinks=False)  # never overwrites a destination
print(f"Restored {binary}; backup retained at {backup}")
PYTHON

This procedure deliberately replaces the selected Bobbin version with the plan’s reviewed release. Compare bobbin --version with the planned version first; keep a newer Cargo install if you do not intend to switch. Do not use cargo uninstall after switching: Cargo still records its previous install and may remove the new entry. For a symlink or a package-manager-owned executable, use its owner’s repair procedure instead of the regular-file steps above.

Opt-in published binary updates

After the initial install and plan review, track published stable releases for a selected binary without waiting for this Caboodle build’s reviewed pins to change:

caboodle update-release --tool bobbin --check
caboodle update-release --tool bobbin
caboodle update-release --tool yupana
caboodle update-release --tool desire-path

This path currently supports Linux x86_64. The tool must already be installed on PATH and selected in the retained plan. --plan and --state select the existing files; the command never starts a new interview or changes the selected profile. Each invocation changes one tool, not the whole stack. Quipu server/CLI source convergence, Camayoc source bundles, and crew runtime updates retain their own contracts and are not silently included.

The updater resolves GitHub’s latest published stable release, requires the named binary archive and checksum asset, checks the downloaded SHA256 and executable’s version, then atomically replaces the PATH entry. Symlink targets are not written through. It retains the previous bytes, runs the existing adapter’s functional verification, and only then records the new version as verified. Failed proof restores the backup. An interrupted update leaves a journal; the next invocation restores its previous artifact before doing anything else, including during a hold.

A numerically newer installed version is preserved. Equal versions with different binary bytes are ambiguous (for example a source build ahead of its release) and are refused. Unreadable versions, missing assets and wrong checksums are errors, never evidence of a current install. The older reviewed-pin update path also refuses to replace a newer installed binary or an ambiguous equal-version build. For initial or missing installations, use apply first.

Create ~/.caboodle/hold to defer new release lookup and installation, or set CABOODLE_HOLD_FILE to a shared host hold path. The updater uses an OS-held lock beside the state file to serialize release updates; backups are retained under release-backups/<binary>/<sha256> in the same directory. A host scheduler must bound total runtime and retain stdout/stderr. This command does not itself install a timer or claim that another host or a long-running process has updated.

caboodle update-self applies the same release, checksum, downgrade, backup and interruption-recovery controls to Caboodle itself. Its post-install contract requires the new binary’s version and update-release --help to answer correctly. Self-update supports Linux x86_64, macOS x86_64, and macOS arm64, selecting the matching published archive and its .sha256 asset. Other platforms refuse before release lookup. The extracted executable is written as a new file rather than restoring archive metadata, so archive extended attributes are not installed. Event consumers can select an exact stable release with update-self --tag v0.2.14. To bind the download to evidence obtained before installation, also supply --archive-sha256 and --binary-sha256. Both values must be lowercase SHA256 digests; both flags require the exact tag and each other. CABOODLE compares the archive before extraction and the executable before running it or replacing the installed copy. A mismatch leaves the previous installer and state intact.

Use update-self --check to inspect metadata; it does not download artifacts or prove either supplied hash. Bootstrap from a published release before scheduling this command; a same-version source build with different bytes is deliberately refused rather than silently overwritten. It replaces the copy PATH runs; when you invoke a different copy by its full path, it says that copy was not changed.

Installed files

  • Binaries in ~/.cargo/bin (or $CARGO_HOME/bin). Keep that directory on your PATH, ahead of any other directory that holds an older copy of the same tool (~/.local/bin is the usual one). The shell runs the first copy it finds.
  • Unpacked releases under ~/.local/share/caboodle/.
  • One setting, [quipu.owl] reactive_materialize = true, merged into ~/.config/bobbin/config.toml. Other settings in that file are kept.
  • In the directory you ran it from: caboodle-plan.toml and .caboodle/ (interview, state, queued install records).

Every verification check uses a throwaway directory, camayoc’s included. The camayoc check starts its own quipu-server on a free localhost port with a temporary store, loads camayoc’s vocabulary and one marker node into it, and stops it afterwards. It never touches the server at QUIPU_SERVER. To set camayoc up against your real quipu server, run the bundle’s bootstrap yourself as a separate step: QUIPU_SERVER=<your server> bash ~/.local/share/caboodle/camayoc/<revision>/scripts/bootstrap.sh.

To remove caboodle, delete ~/.cargo/bin/caboodle and the .caboodle/ directory. Installed tools stay, because other workflows may use them. The install guide covers release pins, source installs, and updates.

Platform prerequisites

Platforms. Linux x86_64 needs no Rust toolchain: each tool comes from a checksummed release or source archive, and Go builds desire-path. On other platforms, availability depends on the tool as shown below; caboodle doctor prints the exact source-build command where no reviewed release exists.

Linux x86_64macOS arm64macOS x86_64Linux arm64
caboodlereleasereleasereleasebuild with cargo install --git
quipureleasereleasereleaserelease
camayocsource archivesource archivesource archivesource archive
bobbinreleasereleasereleaserelease
yupanareleasereleasereleasebuild with cargo
desire-pathbuilt with Gobuilt with Gobuilt with Gobuilt with Go

Commands on PATH. curl, tar, git, bash, python3, and sha256sum (brew install coreutils supplies sha256sum on macOS). Yupana verifies its three pinned v0.12.7 archives in-process and needs only curl and tar for installation. The everything profile also needs Go 1.22+. Rust is only needed for the source builds above.

Accounts and servers. None. Nothing talks to a private network, and no token is needed to install. A token (QUIPU_AUTH_TOKEN) matters only when you send install records to a remote quipu with flush-episodes.

Seeds successful-update rollback

rollback-seeds restores only the prior Seeds artifact retained by the last successful update-release --tool seeds. It requires the same selected plan, state file, executable path and exact current/prior SHA256 values:

caboodle rollback-seeds --plan crew.toml --state state.json \
  --current-sha256 <installed-sha256> --prior-sha256 <retained-prior-sha256>

The successful update records a Seeds receipt bound to the plan, state bytes, paths, versions and both artifact hashes. Missing receipts, changed state, wrong hashes, corrupt backups, symlinks, held updates and lock contention refuse before replacing Seeds. A hold is any directory entry, including a dangling symlink. Unknown hold-path configuration or metadata errors block execution; only a definitely absent entry permits it. Other tools retain their state. A successful rollback runs the existing isolated functional verifier and consumes the receipt. A failed rollback verifier restores the current artifact and retains the state. An interrupted rollback uses normal release journal recovery, restoring the current artifact and invalidating only Seeds verification; it must earn fresh proof before a later operation. This is not a general downgrade override.

Legacy updates without this receipt do not qualify. A private implementation or test does not authorize publication or adoption; follow the applicable review and deployment contract before using it on an installed tool.

Maintainers can verify an embedded Rust release member before publishing a pin:

caboodle verify-member seeds

This stages the archive pinned by that Caboodle checkout, checks its digest, identity and version, and runs the manifest’s functional steps in temporary roots. It does not install a managed program. The caboodle-verify CI job runs this command against the PR checkout; Seeds pin automation requires that exact check to complete successfully, alongside the rest of CI.

Using it with agents

Caboodle is a CLI that an agent can run with the Tambo skill. It does not expose its own MCP server. MCP (Model Context Protocol) lets an agent call servers supplied by the installed tools.

Standalone agent

After installing the retrieval profile, register Bobbin with Claude Code. Run it from the repository to index, and register by absolute path: MCP clients launch servers without your shell’s PATH, so a bare bobbin fails with ENOENT:

claude mcp add bobbin -- "$(command -v bobbin)" serve "$PWD"

For code-intel or everything, also register Yupana:

claude mcp add yupana -- "$(command -v yupana)" serve
claude mcp list

Index a repository before searching it: run bobbin init and bobbin index in that repository. Other MCP clients use the same executable and arguments in their client-specific server configuration.

Managed crew

For a configured Shantytown rig, caboodle project-settings delegates registration to Shantytown and its Quipu tooling manifest. It does not launch or restart agents. See registering MCP servers for prerequisites, selection flags, remote proxies and the policy-only mode.

Agent identity and write access

Agents that write to a shared Quipu store sign each write with their own key. No shared bearer is sent. The operator who runs the store decides which keys may write, and can withdraw that permission for one agent without touching any other.

One identity per agent

Run each agent as its own operating-system user. At minimum, give each agent its own HOME and its own ~/.config. Never share one login between agents. This buys you three things:

  • Attribution. Every write records the agent that signed it, so the history says who did what.
  • Revocation. You can revoke one agent’s key and leave every other agent working.
  • Blast radius. A leaked key exposes one agent’s write permission, and only until you revoke it.

Never copy a key file between hosts or between agents. A second machine, or a second agent on the same machine, gets its own key.

1. Create the key on the agent’s machine

As the agent’s user, run sd key init. The introducer is the person or lead who will register the key on the Quipu host. It is never the agent itself.

sd key init --introducer <operator> --session <agent>-<host> --agent <agent>

sd key init writes one owner-only (mode 0600) private key file under ~/.config/seeds/keys/ and never overwrites an existing key. It prints:

  • the public key and its key_id;
  • the lines to add to ~/.config/seeds/config.toml;
  • the one-time quipu attest register command for your operator.

Only the public key leaves the machine. sd key show prints the same details again later.

Add the printed lines to the agent’s user config, together with the server and project the agent works in:

[quipu]
url = "https://quipu.example.org"
signing_key_file = "/home/<agent>/.config/seeds/keys/<agent>-<host>.key"
signing_session = "<agent>-<host>"
signing_introducer = "<operator>"

[project]
graph = "https://example.org/project/<name>"
prefix = "<prefix>"

Until the key is registered, a write is refused with a message that names the fix:

signed write refused (unbound). The server has no binding for this key and
session: run `sd key show` and have your introducer run the `quipu attest
register` line it prints.

2. Register the key on the Quipu host

The operator runs the printed command once, as the user that owns the store, and adds --db with the store path:

quipu attest register --agent <agent> --session <agent>-<host> \
  --public-key <hex> --introducer <operator> \
  --issued-at <epoch> --expires-at <epoch> --allow-write \
  --db /path/to/quipu.db
quipu attest list --db /path/to/quipu.db

attest list shows one row per binding, with its key_id, introducer, allow_write, expiry and revoked state. The running server picks up a new binding without a restart.

  • Share-only vs write. Without --allow-write, a binding only lets the store trust shares that this key signed. With it, the key may also sign writes to /knot, /update and /episode.
  • Expiry. Writes stop at --expires-at. sd key init proposes about one year; choose what your policy allows.
  • CLI version. Use the quipu CLI whose version matches the served server (compare quipu --version with the server’s /version). Server packages that ship only quipu-server do not include attest.

3. Verify

As the agent, make one write, then read it back as someone else:

sd create "agent write check"           # as the agent: prints the new id
sd show <id> --json                     # as the operator: created_by is the agent

The server log records the accepted write with auth_outcome authenticated_attested.

4. Lifecycle

ActionCommand (on the Quipu host, with --db)Agent sees
Withdraw write, keep share trustquipu attest deny-write <session>signed write refused (scope)
Restore writequipu attest allow-write <session>writes succeed again
Revoke the keyquipu attest revoke <session>signed write refused (revoked)
Rotatethe agent runs sd key init with a new session; register it, then revoke the old sessionwrites succeed with the new key

Revocation is immediate and permanent for that session. To replace a key, mint a new one under a new session name: sd key init refuses to overwrite an existing key file.

Pitfalls

  • quipu-server has no attest subcommand. It ignores the word and starts a server on its default store. Use the quipu CLI on the store host.
  • Do not register your own key. The introducer registers it, so another person or lead vouches for each agent.
  • A fallback credential can hide a missing key. If an old shared-token file, or a symlink to one, is still present on the agent’s machine, older clients may keep authenticating through it. They then never report the missing or rejected identity. Remove such fallbacks once every client uses its own key, and confirm with caboodle doctor, which names the credential source in use.

Worked example: enrolling an agent on a second host

This is a real enrollment, with names and paths replaced. An operator gave the agent on a second host write access to a shared store:

  1. On the agent’s host, as the agent’s own user: sd key init --introducer <lead> --session <store>-<host>-<agent> --agent <host>-<agent>. The agent sent its lead the printed public key and register line, and nothing else.
  2. On the Quipu host, the operator checked the served version with curl -s https://quipu.example.org/version. They then downloaded the quipu CLI of exactly that version from the published GitHub release, and checked its archive against the release’s own .sha256 file. Releases publish one checksum file per archive. They unpacked it into a private temporary directory, not onto PATH, and touched no service.
  3. As the user that owns the store, the operator ran quipu attest list --db <store>, which showed 4 existing rows. Then they ran the register line once, with --allow-write and --db <store>. A fresh attest list showed 5 rows: the new binding with allow_write=true revoked=false, and the 4 earlier rows unchanged. They deleted the temporary directory.
  4. The agent wrote one comment. From a third machine, the lead read it back with the agent as its author.
  5. Rollback, if needed: quipu attest revoke <session> --db <store>, run with a matching verified CLI.

--allow-write grants write permission to the store as a whole. Per-item or comment-only limits are not part of the binding. Do not assume them.

Using caboodle

Caboodle ships a resumable interview and the plan/apply/verify engine with adapters for Quipu, Camayoc, Bobbin, Yupana, and Desire Path.

cargo install --git https://github.com/scbrown/caboodle --locked

# Guided: use, crew themes, and anticipated graph questions are checkpointed.
caboodle init --guided

# Read-only preflight: every blocker on this host, before anything installs.
caboodle doctor

# Non-interactive: read the same schema from caboodle-intent.toml.
caboodle plan --profile retrieval --intent caboodle-intent.toml

# Converge the reviewed plan, then prove both tools with isolated round trips.
caboodle apply
caboodle verify
caboodle verify-questions

# Or run both phases together after review.
caboodle install

apply installs a missing released tool and reads its version back — a successful installer exit alone is never accepted. verify uses temporary isolated stores: it proves a marker absent, writes it, and requires the reader path to return it. Progress is written atomically to .caboodle/state.json, so rerunning converges.

If input ends partway through an interview, Caboodle stops without a plan and keeps accepted answers in .caboodle/interview.toml. Run caboodle init --guided again to resume at the next unanswered question. Invalid answers are rejected and never produce a plan.

Use --skip-install when package installation belongs to another system; the version and functional checks still run.

code-intel adds checksum-pinned Yupana and proves a caller edge in a temporary repository with isolated HOME/state. everything additionally installs Desire Path by building the revision its v0.3.2 tag names, reads it back from dp version, and proves ingest/list against a temporary database. These checks never use the user’s normal evidence stores.

caboodle-intent.toml names the intended use, zero or more themed crew members, and at least one anticipated ontology question. A question is a contract, not a wish: it includes an answer shape, fixture/seed intent, executable SELECT or ASK SPARQL, and an expected result marker. The reviewed install plan embeds the contract, and verify-questions executes it through Quipu’s reader path. This makes “what should the graph answer?” an install acceptance test.

A first install usually has no ontology to ask about yet. Pressing enter at the question-count prompt selects the built-in self-test question instead. It seeds one caboodle-verify-roundtrip node into a scratch store and queries for it, so it passes on a fresh machine. verify-questions always runs it against that scratch store, even when --db points at your own. Replace it with real questions later by editing [intent] in the plan.

intended_use = "answer dependency and ownership questions about my services"

[[crew_members]]
name = "Harbor"
theme = "patient navigator"
domain = "service operations"
role = "trace dependencies and explain incidents"

[[anticipated_questions]]
question = "which services depend on the message broker?"
answer_shape = "a list of service entities"
seed_intent = "two fixture services and one depends_on edge to the fixture broker"
sparql = "SELECT ?service WHERE { ?service <https://example.org/depends_on> <https://example.org/broker> }"
expected = "fixture-service"

The prose fields steer ontology and seed construction; the SPARQL and expected marker decide whether that construction is retrievable through the path a reader will actually use. Never put credentials in this file or the generated plan.

Review observability before deployment

For every selected tool, CABOODLE emits a versioned contract. Quipu and Bobbin currently declare /metrics and required metric families; tools without an exporter remain explicit contracts with an unavailable reason rather than being silently omitted. Supply generic Prometheus targets without URL schemes:

[targets]
quipu = "quipu.example:8080"
bobbin = "bobbin.example:3030"

Then render and inspect the artifacts. Rendering never claims the targets were scraped or deploys anything:

caboodle render-observability --targets caboodle-observability.toml
caboodle validate-observability
less caboodle-observability/prometheus.yml
less caboodle-observability/alerts.yml
less caboodle-observability/dashboard.json
less caboodle-observability/contracts.json

Validation fails on a missing or malformed selected target, a target for a tool whose pinned contract has no exporter, a missing scrape job, or an alert or dashboard metric absent from the versioned contracts.

Reference

The quickstart covers init, plan, doctor, and install. The rest of the command surface, for reviewed updates, shared graphs, observability, and install records:

# Build caboodle from source instead of the release installer.
cargo install --git https://github.com/scbrown/caboodle --locked

# Report what would block an install here. Changes nothing; exits nonzero on a blocker.
caboodle doctor

# Answer the use-shaped interview. It asks who the themed crew members are and
# which questions the finished graph must answer, then only writes a plan.
caboodle init --guided

# Or supply the same intended-use contract non-interactively.
caboodle plan --profile retrieval --intent caboodle-intent.toml

# Optionally add canonical Quipu share directories to the reviewed corpus.
# The target database is explicit; apply stages imports but never promotes them.
caboodle plan --profile retrieval --share ./team-share --quipu-db ./knowledge.db

# Converge the reviewed plan, then prove each tool with a round trip.
caboodle apply
caboodle verify
caboodle verify-questions

# Or run both phases together after review.
caboodle install

# Ask whether the running stack matches this Caboodle build's reviewed pins.
# The command exits nonzero when anything is missing, unreadable, or drifted.
caboodle check-updates

# Converge only drifted tools, read every version back, and functionally verify
# each changed tool before recording it as current.
caboodle update

# A full tool + crew plan is explicit and reviewable.
caboodle plan --profile everything --crew both

apply installs a missing released tool and reads its version back; Bobbin comes from its checksummed release bundle (including its runtime), not a source build. A successful installer exit alone is not accepted. verify proves a marker is absent first, writes/indexes it, and then requires the reader path to return it. Every check uses a temporary isolated store; camayoc’s starts and stops its own scratch quipu server (see installed files). Progress is written atomically to .caboodle/state.json, so rerunning converges and preserves a still-current verified result.

check-updates compares the running identities with the release versions and source revisions pinned in the selected plan’s Caboodle build. It does not use an unreviewed “latest” endpoint. update is the mutation half: it installs only drifted selections through their existing checksummed/source-pinned adapters, requires version read-back to match the reviewed identity, runs the adapter’s functional proof, and only then records an updated transition. A second run is current and performs no installation. This keeps release selection in review while making deployed-versus-released drift executable rather than anecdotal.

If the interview loses input or its caller stops, rerun caboodle init --guided: accepted answers resume from .caboodle/interview.toml. The session disappears only after the complete plan has been written successfully.

The interview works backward from expected use. Each crew member has a free-form theme, domain, and role rather than a closed role enum. Each anticipated ontology question records its expected answer shape, the seed facts needed to exercise it, an executable SELECT/ASK query, and a result marker. caboodle verify-questions runs those reader-path checks after installation. Empty, duplicate, credential-bearing, or non-executable entries are refused before a plan is written.

Installable profiles today are kg (Quipu + Camayoc), retrieval (plus Bobbin), code-intel (plus Yupana), everything (plus Desire Path and, when selected with --crew, Shantytown/Creel), and crew (Shantytown, Creel, both, or standalone). CABOODLE installs checksum-pinned Shantytown and Creel distributions. Creel verification remains browser-owned: it requires explicit machine-readable doctor and admission documents and refuses missing, unknown, unredacted, or non-admit evidence. See the crew capability contracts.

Until Camayoc publishes core.pendant, CABOODLE installs its checksum-pinned bootstrap distribution: ontology, shapes, queries, and the same fail-closed gate proof. Verification then proves a separate first ingest with an absent control, reader-path retrieval, and an idempotent replay. Use --skip-install when package installation belongs to another system; the version and functional checks still run.

Yupana installs from its checksum-pinned v0.12.7 release and proves callers on an isolated fixture repository. CABOODLE builds Desire Path from the revision its v0.3.2 tag names (1f457ec), stamps that identity into dp version, and proves an isolated ingest/list round trip. Neither verification can write into the user’s normal Yupana state or Desire Path DB.

caboodle render-observability converts the reviewed selection plus explicit generic targets into Prometheus scrape config, starter alerts, a dashboard, and versioned contracts. validate-observability fails closed on missing targets or uncovered metrics; rendering is review-only and never claims a live scrape.

Applied/verified transitions and br JSONL lifecycle snapshots queue as redacted, content-addressed Quipu episodes. flush-episodes requires an HTTPS endpoint plus an environment-only token, proves a query marker first, and keeps the identical bytes pending on ambiguous delivery.

Profiles can also consume one or more directories produced by quipu share. Caboodle passes each directory unchanged to quipu import, records the returned share ID, staging graph, eligibility, and blockers in .caboodle/state.json, and leaves ROOT promotion to the explicit quipu import promote review step. Non-conforming or off-vocabulary shares remain quarantined and visible rather than being reshaped into a Caboodle-specific bundle.

Command and option index

Run caboodle <command> --help for that command’s complete option syntax. The table covers the current source tree; published binaries may lag new commands.

CommandPurpose and options
doctorRead-only preflight; --plan.
initInterview; --guided, --output, --session.
planWrite selection; --profile, --crew, --intent, --output, --share, --quipu-db, --quipu-flavor, --embedding-model, --quipu-mcp-url, --quipu-mcp-token-file.
applyInstall; --plan, --state, --skip-install.
verifyFunctional proof; --plan, --state, --creel-doctor, --creel-admission.
installApply then verify; all apply and verify options.
check-updatesCompare reviewed pins; --plan.
updateConverge reviewed pins; --plan, --state, --creel-doctor, --creel-admission.
update-releaseUnix release update; --tool, --check, --plan, --state. Quipu tracks a reviewed paired archive pin; Camayoc/Shantytown refuse without checksummed publications.
update-selfUnix self-update; --check, --state.
bump-memberMaintainers: re-pin a members/*.toml to its published release; manifest argument, --tag, --probe-wrapper.
project-settingsRig registration; --root, --agent, --registry; or --policy-only with --plan, --output.
verify-questionsExecute intent questions; --plan, --db.
render-observabilityGenerate reviewable artifacts; --plan, --targets, --output.
validate-observabilityCheck artifact contracts; --output.
queue-brQueue lifecycle records; input JSONL argument, --queue.
flush-episodesDeliver queued records; --queue, --endpoint.
packs trustPendant autoload trust: own <host/owner>, allow <repo> <sha256>, allow-endpoint <repo> <url>, list, check <remote> <sha256>, check-endpoint <remote> <url> (exit 3 when refused); --file.

Files and configuration

The configuration reference documents plan, state, intent, environment variables and observability targets.

Words you will see

wordmeaning
plancaboodle-plan.toml, the reviewed list of what will be installed. Nothing installs without one.
state.caboodle/state.json, what is installed and when it last passed its check.
negative controlThe half of a check that proves the thing is absent first, so a pass could have been a fail.
episodeA batch of facts written to quipu in one go.
knotOne write to quipu over HTTP (/knot).
ontology, shapesThe vocabulary quipu accepts and the rules (SHACL) that reject bad facts.
share, pendantA portable export of a quipu graph that another store can import. “Share” names the protocol; a pendant is the artifact that travels.
crewSeveral named coding agents that work together, run by shantytown (terminal) or creel (browser).
bead, brAn issue in beads, a git-friendly issue tracker agents use as memory.
MCPModel Context Protocol, how an agent calls a tool’s server.

Quipu paired release updates

caboodle update-release --tool quipu --plan caboodle-plan.toml selects the exact version and per-target SHA256 in release-pins/quipu.toml, rather than implicitly trusting the latest release or its downloaded checksum. The publication must provide the expected archive and checksum exactly once; both the archive bytes and published sum must agree with the reviewed digest.

The plan must select a local release-flavor Quipu. Both quipu and quipu-server must be regular files selected by PATH in CARGO_HOME/bin (default $HOME/.cargo/bin), with matching versions. External servers, LanceDB source builds, shadowed paths, symlinks, downgrades, mixed versions and equal versions with different bytes are refused. The install does not restart any service or load data into a running store.

Both executables are backed up before either is replaced. The existing release lock and pending journal protect the pair; failed installed verification or an interrupted swap restores both prior binaries and the exact prior state. A corrupt backup retains the journal and refuses recovery before changing either file. Backup file renames and every ancestor directory entry are synced before the pair journal becomes durable; its removal is synced before success. A failed clear barrier reports uncertain completion and attempts to retain the exact pending record for later recovery. Failed retention remains an error, never a claim that completion or restoration is proved. Existing single-binary journals and Seeds rollback remain supported. This does not extend rollback-seeds to other tools.

Verification performs the existing negative/positive client and vocabulary round trips in temporary stores and checks the server version/help contract. It does not prove a running server, service restart, database migration or host delivery. --check inspects publication metadata without downloading or replacing executables and explicitly reports that checksum/functional proof has not run. Source pin updates still require review; no automatic pin writer or host schedule is introduced by this command.

Camayoc has no published release, and Shantytown’s wheel release lacks a checksum asset. Their explicit refusals preserve these missing trust anchors; source bundles and wheels are not guessed into standalone binaries.

Configuration reference

Caboodle writes a reviewable TOML plan and a JSON progress file. Use the CLI to create the plan; keep credentials in the environment, never in intent text. The plan’s schema rejects unknown keys.

Plan: caboodle-plan.toml

KeyMeaning
schema_versionPlan schema version, currently 1.
profilekg, retrieval, code-intel, everything or crew.
toolsOrdered tool selection derived from the profile.
sharesCanonical Quipu share directories to stage during apply.
quipu_dbExplicit destination database, required when shares are selected.
quipu_flavorrelease (default) or lancedb; see profiles.
stack_config.quipu.owl.reactive_materializeRecommended OWL materialization setting merged into Bobbin’s config.
crew.modeshantytown, creel, both or standalone.
crew.durable_ownerShantytown for durable crew work, when selected.
crew.burst_ownerCreel for browser bursts, when selected.
crew.routingsingle-owner or explicit-handoff, derived from the mode.
crew.policy.identity_sourcequipu; crew identity authority.
crew.policy.modelOptional model selection.
crew.policy.toolsThe crew profile’s tool set.
intentIntended-use contract described below.
embedding_model.destinationDirectory for the model artifacts.
embedding_model.artifactsArray of entries, each with name, HTTPS url and sha256.

Crew ownership fields must match the selected mode. Do not hand-edit them into an alternative routing scheme. Profiles explains the contracts.

Intent: caboodle-intent.toml

KeyMeaning
intended_useNonempty plain-language purpose.
crew_membersArray with name, theme, domain, role; may be empty.
anticipated_questionsAt least one executable reader-path question.
anticipated_questions[].questionThe question in words.
anticipated_questions[].answer_shapeShape of the expected answer.
anticipated_questions[].seed_intentFacts required before the query can pass.
anticipated_questions[].sparqlSELECT or ASK query.
anticipated_questions[].expectedMarker required in its result.
anticipated_questions[].self_testBuilt-in scratch-store check flag; normally generated by the interview.

plan embeds the intent in the plan. verify-questions executes those queries; custom questions require their seed facts in the target database first. The built-in interview self-test instead seeds its own temporary store. Installing and verifying tools does not seed a user’s production graph.

Local state and targets

.caboodle/state.json is generated progress, not user configuration. It records tools, model artifacts and staged shares. Preserve it to resume an interrupted apply or update. .caboodle/interview.toml checkpoints interview answers; .caboodle/episodes/ retains queued install records until confirmed delivery.

caboodle-observability.toml contains a [targets] table with quipu and, when selected, bobbin keys. Values are Prometheus host:port destinations, without a URL scheme or path. Rendering produces files for review, not a live scrape. See emission and proofs for those boundaries.

Environment

VariablePurpose
HOMEUser configuration, data and default install directories.
CARGO_HOMEOverrides the binary destination root; binaries go in its bin/.
PATHMust find the installed binaries before stale copies.
CABOODLE_CAMAYOC_ROOTOverride the Camayoc distribution root.
QUIPU_SERVERExisting Quipu server for operations that need one; temporary functional checks remain isolated.
QUIPU_AUTH_TOKENNonempty bearer override for Quipu requests.
QUIPU_AUTH_TOKEN_FILERequest-time token file; defaults to ~/.config/quipu/token.
CABOODLE_HOLD_FILEOverride the release-update hold marker, default ~/.caboodle/hold.
SHANTY_TOOLING_MANIFESTShantytown’s managed rig registration manifest; see agent setup.

The shell release installer also accepts CABOODLE_VERSION, CABOODLE_INSTALL_DIR, CABOODLE_TARGET, CABOODLE_REPOSITORY and CABOODLE_RELEASE_BASE_URL. The last two select the download authority; keep them unset for official releases. These are installer inputs, not plan keys.

Quipu credentials

Obtain an accepted credential from the target server administrator through your approved secret distribution channel. The administrator activates the shared bearer or registers an issued named credential before distributing it. Locally installing a random token does not grant access.

install -d -m 700 "$HOME/.config/quipu" && install -m 400 /secure/issued-token "$HOME/.config/quipu/token"
caboodle provision-quipu-token --from /secure/issued-token --server https://your-quipu-server.example
caboodle doctor

provision-quipu-token proves the issued file against the authenticated server before installing it. It refuses to replace a differing existing credential; rotation remains a separate decision. It creates the canonical file with mode 0400 and its parent with mode 0700. No token is printed or stored in a plan.

A nonempty QUIPU_AUTH_TOKEN after trimming takes precedence over QUIPU_AUTH_TOKEN_FILE, then the default ~/.config/quipu/token. Files are read on every request so rotation reaches running sessions. An explicit file does not fall back to the default if missing. Existing deployments can retain their file override until provisioning and rotation move together; avoid independent copies of secrets. Token contents must never enter command arguments, logs or version control. Doctor and verify name the winning source without printing its value, including an environment value that shadows a valid canonical file.

Doctor checks the protected /episode route with {}. The server authorizes before rejecting the incomplete episode, so no graph data is written. It reports accepted authorization, missing token, rejected token, read-only server, or an unknown write path (including no HTTP response). This is authorization evidence, not a successful storage commit. Doctor also requires a nonempty canonical file with mode 0400 and parent mode 0700, and an authenticated /shapes read returning 2xx. caboodle verify runs that live credential proof before recording Quipu as verified, then exercises installed tools in isolated scratch stores. Set QUIPU_SERVER to the intended server; without it, verify uses the plan’s Quipu MCP URL or http://localhost:3030. Missing, rejected, or unproven credentials leave verification incomplete. Public health is not an auth check.

Quipu MCP for Claude Code

A Claude Code MCP entry for Quipu that holds only a URL sends no bearer. Reads work and every MCP write is refused with missing_or_invalid_bearer_token. caboodle plan --quipu-mcp-url <server> adds a reviewable [quipu_mcp] step. That step changes your Claude Code configuration, so it is opt-in.

apply installs ~/.local/bin/quipu-mcp-headers (mode 0755). Claude Code runs it at connect time. It reads the token with the precedence above and prints the Authorization header. apply then registers the user-scope entry {"type":"http","url":"<server>/mcp","headersHelper":"<helper>"} through claude mcp add-json. The secret is written into neither the helper nor the Claude configuration. Re-running is a no-op. A differing user-scope quipu entry, including one without a headersHelper, is replaced, and the output says so. If registering the new entry fails after the old one was removed, the old entry is put back. A running Claude Code session keeps its old connection until /mcp reconnects it.

Claude Code runs the helper in its own environment, which may not carry the token variables. If this host’s token file is not ~/.config/quipu/token, give its path with --quipu-mcp-token-file. plan also takes it from QUIPU_AUTH_TOKEN_FILE when that is set. The path, never the token, is recorded in the plan and becomes the helper’s default. QUIPU_AUTH_TOKEN and QUIPU_AUTH_TOKEN_FILE still take precedence when Claude Code has them.

verify fails if the user-scope entry is missing, differs from the plan or has no headersHelper. It also fails if a local-scope quipu entry without one shadows it. It then sends an invalid-Turtle quipu_knot call to /mcp, first without a credential as a control, which must be refused. It sends it again with the helper’s header, which must reach Quipu’s parser. Nothing is written. A server that accepts the unauthenticated control reports UNKNOWN, because it cannot show that the helper works. Verify runs in your shell, not in Claude Code. If the helper finds a token only through a variable set in that shell, it warns that Claude Code must see the same variable. Re-plan with --quipu-mcp-token-file if Claude Code will not see it.

Codex search signposts

The desire-path hook bundle supplies dp signpost-prefetch before Codex Bash calls and dp signpost after successful calls. Codex uses the same tool_name: Bash and tool_input.command hook envelope for these events. Turn-completion ingestion remains in codex_notify with --source codex; these search hooks do not convert turn records into tool-failure desires. Failure-event signposting is not claimed by this bundle for Codex.

Set DP_SIGNPOST_BOBBIN_URL, DP_SIGNPOST_LOG, and any deployment-specific DP_SIGNPOST_SEARCH_MODE in the environment that launches Codex. Values in Claude’s settings.json environment do not configure Codex. Verify a real search produces a signpost event under the Codex session ID and that prefetch warms the cache; a configured hook alone does not prove delivery.

Codex edit policy hook

The Yupana bundle registers yupana hook pre-edit for native apply_patch PreToolUse events. This requires a Yupana build with native multi-file Codex patch support; a pre-support binary silently ignores the patch contents. Activate this entry only after the supporting Yupana release is installed. Verify a real edit’s session-attributed guard metric and advisory output; rendered configuration alone does not prove edit coverage. Existing running agents pick up changed hooks on their next launch.

Externally owned Quipu

Set external_quipu = true in a reviewed plan when another mechanism owns Quipu installation. The default is false. Caboodle resolves quipu and quipu-server from PATH, reads both versions, and records their canonical paths with the identity. It never installs, converges, or release-updates these files. Missing executables and incompatible versions fail; external ownership does not waive the isolated write/read round trip or hook registration checks. Other tools retain their managed-install and shadow checks. Build flavors are incompatible with external ownership because Caboodle does not build that installation.

Verify uses the resolved client for both the entity and vocabulary round trips. An external source build has to support those contracts; a version banner alone is insufficient. check-updates reports external ownership rather than claiming release convergence. Apply records identity, while verify records functional proof. Changing executable paths invalidates retained verification even if the version strings match.

Architecture

Caboodle’s engine is a plan → apply → verify loop with a state file recording what is installed and last proven working. Idempotent and resumable: re-running converges, and a failed step names itself.

The wizard is an interview, not a form over install switches. caboodle init --guided first elicits intended use, themed crew-member identities/domains/roles, and the questions the installed graph must answer. Those answers become typed plan data and executable reader-path contracts. The engine therefore builds backward from expected retrieval rather than forward from whatever data happens to be available.

In agent hands the same flow is a skill; non-interactive callers provide the same schema in caboodle-intent.toml. Output is always the plan file — two-phase, so nothing installs on the strength of a conversation alone.

Three proofs, not one

Three claims separate caboodle from an install script, and each is a proof:

  1. Installed — proven by version read-back, never by exit code.
  2. Working — a per-tool functional round-trip: quipu accepts an episode and a control-gated query finds it; bobbin indexes a fixture and search returns it. Every check pairs with a negative control — the selftest that corrupts a byte and goes red — so a pass is capable of failing.
  3. Observable — Prometheus actually scrapes each tool’s declared exporter; caboodle generates the scrape config, a starter alert pack, and one consolidated dashboard.

This encodes the operating culture the stack was built under: verify by mechanism, read back what you wrote, control every zero.

Convention over manifest

There is no per-repo manifest file to parse or rot. Each tool is caboodle-ready by exposing the pattern the repos already follow:

  • conventional CLI verbs: <tool> selftest, <tool> health, <tool> init
  • and/or just recipes: just install, just verify

Caboodle discovers and drives these directly. Metrics contracts live as camayoc catalogue entries, not per-repo config. The readiness contract every corpus repo adopts:

  1. The conventional CLI surface above.
  2. A selftest with a negative control.
  3. A metrics story, catalogued in camayoc.
  4. Versioned release artifacts — install never means build-from-source.
  5. init writes a starter config; file + env with documented precedence.
  6. A health probe distinct from the selftest.
  7. No environment-specific names in defaults or docs.

Profiles

  • kg — quipu + camayoc
  • retrieval — + bobbin
  • code-intel — retrieval + Yupana; isolated analyze/callers proof
  • crew — a choice: shantytown, creel, both, or standalone (no crew layer — the tools serve a single agent or a human directly). A tmux-resident crew on a host, parallel agent bursts in the browser, the pair, or neither.

Both harnesses are first-class: Claude Code and codex can each drive the install, and when a crew is chosen the wizard asks which harness each role runs on, emitting the right per-harness configuration. The crew profile also installs seeds (sd), the crew’s work tracker, and shuttle, its workflow engine.

  • everything — code-intel + Desire Path + seeds + shuttle; isolated failure-ingest/read proof, a seeds marker round trip, and a shuttle run round trip

When a plan selects both seeds and Desire Path, apply also sets the Desire Path command alias bd -> sd, so an agent that types bd from habit reaches seeds. verify checks that the alias is there. If the host already has a bd alias that points somewhere else, caboodle leaves it alone: dp alias would silently replace it, so caboodle reads the existing aliases first. apply and verify then report the existing target as a deliberate local choice, not a failure. If dp is not on PATH, apply prints a note instead of failing.

Any profile can extend its corpus with repeatable --share selections because all profiles include Quipu. Shares require an explicit --quipu-db, are staged through Quipu’s canonical import command during apply, and are never promoted to ROOT automatically. The share choice is part of the reviewable plan rather than a hidden installer input.

The crew choice is emitted today:

caboodle plan --profile crew --crew shantytown
caboodle plan --profile crew --crew creel
caboodle plan --profile crew --crew both
caboodle plan --profile crew --crew standalone

both is not fan-out. Its plan names Shantytown as durable_owner, Creel as burst_owner, and explicit-handoff as the only routing mode. This keeps one owner per task until the cross-harness handoff contract lands.

The reviewed plan can export policy summaries for review. These summaries do not register MCP servers or override the rig crew and its Quipu tooling manifest:

caboodle project-settings --policy-only

In both mode this writes shantytown.settings.json and creel.settings.json. Their shared object is byte-equivalent. Their security surfaces are intentionally not: Shantytown owns hooks and host-workspace access; Creel owns browser-BYO credentials (write-only to agents) and operator-granted browser permissions. Neither adapter is allowed to emit the other’s fields.

The engine applies and verifies Quipu + Camayoc for kg, adding Bobbin for retrieval, Yupana for code-intel, and Desire Path for everything. A crew plan then applies only the selected runtime: the released Shantytown wheel, the static Creel browser bundle, both in their declared owner roles, or neither for standalone. Both distributions are checksum-pinned.

Shantytown verification reads back st 0.4.0. Creel’s browser security and admission facts cannot honestly be inferred by the host-side installer, so its adapter consumes the versioned crew capability contracts. It refuses when either contract is absent, when a required doctor result is not pass, or when the governor does not return a measured admit.

Camayoc currently distributes its bootstrap ontology, shapes, queries, and gate proof as a repository bundle; CABOODLE pins and checksums that bundle until the designed core.pendant artifact is published.

Quipu install flavor

Every profile installs Quipu, and the plan’s quipu_flavor field decides how. The default, release, installs the reviewed release feature set, which deliberately excludes the lancedb cargo feature — so vector.backend = "lancedb" refuses at startup on a default box. The lancedb flavor builds the same reviewed revision with --features full,lancedb; only the feature list changes, so choosing the flavor can never pull in an unreviewed Quipu:

caboodle plan --profile retrieval --quipu-flavor lancedb

The flavor is proven, never assumed. quipu --version cannot say which features were compiled in, so verification asks the running Quipu server’s GET /version per-feature compile map and goes red when the plan asked for lancedb and the map lacks it — or when the server reports no map at all, because an unprovable claim is a refusal, not a pass. An installer exit code is never accepted as the proof. Because the flavor is invisible to plain version read-back, check-updates cannot see flavor drift; verify is the command that catches it, and its failure names the exact reinstall command that converges.

Embedding model artifacts

Quipu deliberately bundles no model weights (its onnx feature ships the runtime only), so a box that should embed needs the artifacts provisioned — and that belongs in the reviewed plan, not in an ad-hoc download script. The plan’s [embedding_model] section names a destination directory and a list of artifacts, each pinned by URL and sha256:

[embedding_model]
destination = "/var/lib/quipu/embedding-model"

[[embedding_model.artifacts]]
name = "model.onnx"
url = "https://models.example/reviewed/model.onnx"
sha256 = "…64 lowercase hex characters…"

[[embedding_model.artifacts]]
name = "tokenizer.json"
url = "https://models.example/reviewed/tokenizer.json"
sha256 = "…"

caboodle plan --embedding-model <spec.toml> copies a spec of that shape into the plan. During apply, each artifact is downloaded beside its destination and hashed before it may take the artifact’s name; a checksum mismatch deletes the download and fails the named embedding-model step with a nonzero exit. An artifact already on disk with the pinned digest is a recorded no-op, so re-running converges without re-downloading weights. Provisioned artifacts land in .caboodle/state.json under models, exactly like every other step.

What verify proves today, stated plainly: verification re-hashes every artifact on disk against the plan’s pinned digest and goes red on drift or absence. It does not yet prove an embed-and-search round-trip — the installed Quipu exposes no reviewed embeddings contract for caboodle to drive, and wiring a proof that cannot fail would be exactly the banner the three-proofs rule forbids. When that contract lands, the round-trip (auto-embedded episode against an isolated store, absent-control first, search finds it) is the check that belongs here.

Crew capability contracts

CABOODLE installs the selected crew runtime, but it does not take ownership of the runtime’s security or admission policy. Shantytown is a host executable, so its adapter reads back the installed st version. Creel’s decisive facts exist inside the browser; verification therefore consumes two browser-produced JSON documents:

caboodle verify \
  --creel-doctor creel-doctor.json \
  --creel-admission creel-admission.json

Both schemas are version 1 and reject unknown fields. A new producer field cannot silently become evidence an older CABOODLE claims to understand.

Doctor

{
  "schema_version": 1,
  "overall": "pass",
  "checks": [{
    "id": "secure-context",
    "status": "pass",
    "severity": "required",
    "evidence": "browser reported a secure context",
    "remediation": "serve Creel over HTTPS",
    "redacted": true
  }]
}

Check IDs must be non-empty and unique. Status is pass, fail, or unknown; severity is required or advisory. Every check carries non-empty redacted evidence and remediation. Every required check and the aggregate must be pass.

The Creel doctor workstream owns which checks it emits, including secure context, persistence, popup/bridge permissions, provider credential presence, Quipu WASM, state-repository health, service-worker freshness, and leases. CABOODLE validates the contract; it does not reproduce those probes.

Admission

{
  "schema_version": 1,
  "verdict": "admit",
  "provider_window": {"status":"pass", "evidence":"below ceiling"},
  "device_tab_cap": {"status":"pass", "evidence":"one slot available"},
  "signal_freshness": {"status":"pass", "evidence":"observed now"},
  "reason": "launch is within the redacted policy limits",
  "redacted": true
}

The verdict is admit, refuse, or unknown. CABOODLE proceeds only when all three signals are measured passes and the verdict is admit. It never prints credentials, accepts an unredacted reason, rounds unknown to zero, or converts a policy refusal into an installer warning.

Until Creel publishes both producer interfaces, its bundle can be applied but cannot be marked verified. That refusal is the intended boundary, not a CABOODLE installation failure to bypass.

Emitting to quipu

Caboodle is itself a quipu emitter, twice over:

The box emits. Every applied and functionally verified tool transition is written as a redacted episode under .caboodle/episodes/. The filename is the SHA-256 identity of the event content, so rerunning the same transition creates no duplicate. A failed delivery leaves the exact bytes pending; a later replay can safely receive Quipu’s unchanged outcome and then move the file to sent/.

Beads emits. Bead lifecycle — created, closed, commented — flows to quipu as episodes through the same discipline. caboodle queue-br export.jsonl projects each issue row into created, comment, and (when closed) closure events; re-reading a snapshot is idempotent. Because br is upstream, this integration lives in CABOODLE and requires no fork patch.

Flush only after reviewing the queue. The endpoint must be HTTPS (or explicit localhost HTTP), and the bearer comes only from QUIPU_AUTH_TOKEN; it is never written into the plan, queue, or command line. CABOODLE first runs a SPARQL control whose marker must return, then posts files byte-for-byte:

caboodle queue-br br-export.jsonl
QUIPU_AUTH_TOKEN=... caboodle flush-episodes --endpoint https://graph.example

Created, updated, and unchanged are successful delivery outcomes. A transport error, malformed response, or missing outcome preserves the identical pending file instead of guessing whether the write committed.

The stack

    caboodle                      (interview → plan → apply → verify → observe)
        │  installs, proves, and watches ↓
        ▼
  quipu · bobbin · camayoc · yupana · desire-path · seeds · shuttle · shantytown / creel
        │
        ▼
    prometheus                    (every tool's metrics, consolidated)

Caboodle never re-implements a tool’s job. It installs the tool and then makes the tool demonstrate its own job, through three proofs:

  1. Installed: proven by reading the version back, never by an exit code.
  2. Working: a per-tool round trip. quipu accepts an episode and a query finds it; bobbin indexes a fixture and search returns it; yupana finds a caller in a fixture repository; seeds lists a fresh store, creates a marker work item, and finds it; shuttle defines a workflow, starts a run, advances it through one signed transition, and finds it in the new state; quipu also loads the pinned Quechua vocabulary release, which must resolve quechua:WorkflowRun and must not resolve an undeclared term. Every check first proves its marker is absent, so a pass is capable of failing.
  3. Observable: caboodle generates Prometheus scrape config, starter alerts, and one dashboard for the selected tools, for you to review and deploy.

Install and verification outcomes are also queued as quipu episodes, so a fresh box’s first knowledge is a record of the box itself. The agent-facing version of this workflow is the tambo skill, and the full design is in the book.

The Quipu stack is built around a governed, bitemporal knowledge store: shared facts, decisions and work history your agents can query. Quipu can check writes against your rules, records where each fact came from, and answers what was true when.

toolwhat it gives your agents
caboodle🧰 Agent Platform Toolkit - installs and configures the quipu stack
quipustores shared facts with provenance and history; can check writes against your rules
camayocprepares knowledge and vocabulary for Quipu, with checks before it is admitted
bobbinsearches your code and can include related knowledge from Quipu
yupanashows code impact and can read policies from Quipu to check changes
desire-pathturns agent mistakes into feature requests; works on its own without Quipu
seedsa work item tracker backed by the Quipu knowledge base
shantytownruns crews of AI agents on work items, triggered by hand, on a schedule or by events; can share work and memory through Quipu
shuttleruns signed agent workflows and keeps their records in Quipu

Supporting projects

repowhat it is
shantytowna small harness for running a crew of coding agents
creelparallel agent bursts, entirely in the browser
shantya terminal multiplexer wrapper that makes tmux feel like home
beadsissue tracking as agent memory — br, SQLite + JSONL
quechuathe stack’s shared vocabulary; caboodle pins a release by digest and loads it into Quipu (catalog)

See the corpus for share import and profiles for which tools each selection installs.

The corpus

toolrole in the box
quipugoverned knowledge-graph store
bobbinsemantic index · retrieval · RAG
camayocontology bootstrap, ingress discipline, knowledge packs
yupanacode-structure graph — impact, callers, verify
desire-pathhallucinations → feature requests
shantytowncrew harness (multi-agent, tmux-resident)
shuttleworkflow engine — signed runs, windowed export, frozen history
creelbrowser-native crew — tabs as workers
shantythe tmux the crew lives in
beads (br)issue tracking as agent memory — SQLite + JSONL

Extending the corpus with Quipu shares

Every profile includes Quipu and can stage git-native knowledge shares into an explicit local database:

caboodle plan --profile retrieval \
  --share ./team-knowledge \
  --share ./project-knowledge \
  --quipu-db ./knowledge.db
caboodle apply --skip-install

A share directory is the canonical output of quipu share: manifest.json, export.nt, and shapes.ttl. Caboodle does not parse, rewrite, unpack, or wrap those files. It invokes quipu import <directory> --db <database>, so Quipu owns payload-hash verification, identity candidates, local SHACL validation, and the staging/quarantine graph.

The returned share_id, graph, outcome, promotion eligibility, and blockers are retained under shares in .caboodle/state.json. A quarantined import is a successful safe consumption outcome that still needs review; Caboodle never runs quipu import promote. Promotion into ROOT remains a separate explicit operator decision using the share ID.

Roadmap

  • Phase 0 (shipped) — plan/apply/verify engine; Quipu + Bobbin adapters; checksummed release installs; atomic state; clean-machine CI test.
  • Phase 1 (shipped) — LLM-guided interview; skills/caboodle; Camayoc bootstrap and first-ingest proof; Shantytown/Creel crew adapters with browser-owned capability contracts; public clean-machine retrieval proof.
  • Phase 2 (shipped) — metrics contracts, generated Prometheus config, starter alerts, one dashboard; yupana + desire-path adapters; the quipu/beads emitters.
  • Phase 3 — caboodle upgrade; per-repo CI keeping conventional verbs tested with releases; expanded crew routing after explicit handoff lands.

Design: pendant autoload, monorepo shares and graph pointers

Status: repository sync and opt-in hooks are implemented in source. Monorepo derived shares and unloaded graph pointers remain proposals. Source implementation is separate from release, installation and opt-in activation.

Once repositories routinely ship a quipu pack (.pendant.tar.gz), a fresh clone should have that knowledge in the local graph without anyone running a command. This document covers three questions: how a clone discovers and loads its packs, how a monorepo ships one pack per project, and how a pack can point at graphs that are not loaded yet.

What exists today

  • Packs. A .pendant.tar.gz is a deterministic text bundle, not a database file. Its manifest carries parent_share, tx_anchor, graph_hash, shapes_hash and a stored CONSTRUCT scope.
  • Composition. quipu compose loads verified packs into one store. Each pack keeps its own named graph, and their union is named as a dataset. Exact IRIs join across packs; blank nodes stay scoped to their pack. Different shape bundles need an explicit --shapes-from. A nonconforming union stays in quarantine, and nothing is promoted to ROOT. A local retraction survives a reload of the same snapshot. See quipu’s pack composition.
  • Manual loading. install-stack.sh --pendant PATH loads a pack by hand, and scripts/setup-environment.sh stages and verifies packs in cloud environments.
  • Federation. quipu reaches remote graphs through SPARQL SERVICE and federated query fan-out, but only endpoints declared in the federation configuration.

Repository discovery is available through the explicit sync command below. Nothing installs or activates repository hooks without an opt-in.

1. Discovery and autoload

Convention, not a manifest. Caboodle does not parse per-repo manifest files (convention over manifest), and packs need none. A repository ships its packs at one conventional path:

.quipu/packs/*.pendant.tar.gz

Each pack already describes itself: its own manifest, inside the archive, carries the graph IRI, graph_hash, shapes_hash, parent_share and scope. Discovery is a directory listing, and nothing in the repository can drift out of step with the packs it describes.

Triggers. A post-checkout and post-merge hook, installed by tambo init --pack-hooks in repositories the user opts in, runs the same sync command (the caboodle compatibility executable supports it too):

tambo packs sync --repo . --shapes-from /path/to/trusted-authority.pendant.tar.gz

The command fingerprints all discovered archives. Trust is checked on every run, including a replay. When any archive changes or disappears it composes the whole remaining set, not just the changed file. Quipu moves the repository’s CURRENT pointer only on a successful composition. An unchanged receipt skips composition only while its authority, repository identity, database and WAL stamps still match. A missing database triggers a fresh composition. Receipt replay is a local cache check, not a fresh query proving every stored fact.

A trusted authority pendant is required when there are packs to load. Supply one exported from the operator’s reviewed store; it is not selected from the repository’s data or used to install constraints. Its shapes govern the union, and its own facts also participate in the composed dataset, so use an empty-fact authority pendant when only its shapes should be included. Every repository archive is copied to private temporary storage before hashing and composing; Quipu verifies those exact frozen bytes. Sync accepts at most 64 archives and 256 MiB of total archive bytes, including the authority. A refusal or quarantine never becomes a successful-load receipt, and no automatic retry is attempted.

By default the database and receipt live under the Git directory’s caboodle-pendants/; --db selects a different local database. Repository identity comes from origin (host/owner/name), and pack identity is its stable file name. No per-repository manifest is needed. Only .pendant.tar.gz files are discovered; old .qpack names must be migrated deliberately. Symlinked discovery directories and archives are refused.

Opt in to both Git hooks with:

tambo init --pack-hooks --repo . --shapes-from /path/to/trusted-authority.pendant.tar.gz

Existing hooks are retained, never appended to or replaced. If core.hooksPath is configured, have its owner call the sync command there instead. The generated hooks use the chosen executable and authority path. Hook installation does not compose immediately; a later checkout or merge runs sync. Git hooks can be shared by worktrees of one repository. Remove only these generated hooks to opt out; ordinary guided initialization does not install them.

Cloud setup uses the same command when CABOODLE_PACKS_REPO names an available checkout and CABOODLE_PACKS_SHAPES_FROM names its trusted authority. If setup runs before checkout, it explicitly reports that sync did not run: invoke sync after checkout rather than treating the environment cache as an autoload proof. Optional setup failure is reported but still leaves the session’s exit status 0.

Stable names and rollback. Quipu’s delivered repository mode provides urn:quipu:repo:<host/owner/name>:current and :previous, retains both snapshots and carries local retractions across snapshots. Query these dataset aliases with SPARQL FROM, not GRAPH. quipu compose --repo host/owner/name --rollback --db <local-db> swaps the pointers. Sync does not promote anything to ROOT; rollback and broader consumer installation remain explicit operator actions.

2. Trust

A pack is data landing in the graph that agents query first, so loading is a trust decision.

Exists today (aegis-2eckx0): caboodle packs trust keeps these decisions in ~/.config/caboodle/pack-trust.toml, separate from the stack config. owners lists host/owner pairs whose packs autoload. [[allow]] admits one foreign archive and must name both repo and pack_sha256: an entry without the hash, an unknown key or a malformed value refuses the whole file, so every pack is refused until it is fixed. [[allow_endpoint]] admits one exact http(s) endpoint with no credentials in its URL. caboodle packs trust check <remote> <sha256> --pack <path> exits 0 to load, or 3 with a refusal naming the pack and the exact allow command. packs sync asks this question before composing and before accepting a cached replay.

packs trust check-endpoint <remote> <url> asks the same policy whether a pointer endpoint may join federation: exit 0 admits it, exit 3 refuses it. It reads the current trust file on every invocation and never changes it. An archive allow does not admit its endpoint; foreign endpoints need their own exact repository/URL allow. Both tambo and caboodle expose this command.

  • Your own repositories load automatically, meaning repositories whose remote owner matches an allow-list in caboodle’s configuration.
  • A foreign pack needs an explicit allow, recorded per repository and pack hash. A repository name appearing in a configuration file is not consent.
  • Loading is not promotion. An autoloaded pack stays in its named dataset, queryable through the current pointer. It never enters ROOT. Promotion remains a separate, reviewed step, as composition already enforces.
  • Shape authority stays with the store. Sync requires an operator-controlled authority pendant (--shapes-from), supplied independently of repository discovery. Repository packs cannot choose that authority or install constraints.

3. Monorepos

A monorepo ships one parent share and one derived share per project.

  • The parent share covers the whole repository. Each project is a derived share (parent_share set) whose stored CONSTRUCT scope filters by path prefix, such as packages/api/.
  • Each derived pack’s own manifest records its path scope, so caboodle packs sync can reload only the projects whose paths changed in the checkout, the same idea as yupana’s per-file keys, grouped by project.
  • Cross-project edges survive a reload. Entity IRIs do not depend on the commit, so an edge from one project to another still resolves after either side reloads.
  • Sparse checkouts load only the projects present. The projects that are absent can be reached through a pointer (section 4) instead.
  • Ownership: a CODEOWNERS entry becomes owned_by on the project’s entities.
  • Recommendation: no combined all-projects pack at first. Composition of the derived shares already gives the union. Add a combined pack only if cold start is measured to be too slow.

Test case: scbrown/reckoning, a real pnpm monorepo.

The monorepo implementation adds an explicit packs sync --monorepo mode. It reads native share manifests, finds the one parent referenced by the derived shares, and excludes that parent from CURRENT. Each child uses the canonical CONSTRUCT path scope printed by packs project-scope. A child needs a visible tracked file whose index entry is not marked skip-worktree, under a directory without symlink redirects. An excluded directory left behind by untracked files does not count as a checked-out project; those files are preserved. Keep .quipu/packs/ in a sparse checkout. Unchanged members reuse Quipu’s committed pack graphs; changing one share creates only that member’s new graph. Selection is part of the version-2 monorepo receipt, so changing sparse membership reconciles CURRENT. Flat mode retains the baseline version-1 receipt format. Downgrading a monorepo consumer requires its retained pre-upgrade receipt and database rollback; an older binary refuses version-2 metadata rather than silently loading all projects. The parent may itself name a previous snapshot. All archives, including excluded ones, are trust-checked and verified by native Quipu in an isolated temporary store before a changed composition is accepted.

For a producer with an existing graph and loaded shapes, generate the scope, then use native quipu share with the parent share’s identity:

tambo packs project-scope --graph urn:source:repo --path-predicate urn:source:path \
  --prefix packages/client/ > client.construct
quipu share --construct "$(cat client.construct)" --parent-share "$parent_share" \
  --shapes project-schema --no-queries --db producer.db --output client-share

Archive the parent and each child as separate .pendant.tar.gz files under .quipu/packs/. No all-projects union pack or repository manifest is required. The native manifest binds both the query and parent_share into share identity. Scope readers deliberately refuse arbitrary query variants rather than guessing which paths they cover. Regenerate a scope with packs project-scope.

To include CODEOWNERS-derived ownership, add --repo . --db producer.db --ownership-predicate urn:source:owned_by to packs project-scope. Supply the existing schema’s predicates; these URNs are examples. The command reads the first CODEOWNERS in .github/, the repository root, or docs/, applies the last matching rule to tracked project files, and includes ownership in the native CONSTRUCT. It supports GitHub users, teams and email owners. Empty owner rules clear earlier matches. Unsupported patterns (!, bracket ranges and backslash escapes), invalid identities, symlinked CODEOWNERS, and bounded input overflows refuse. When CODEOWNERS is absent it invents no owners. Git’s matcher runs in a private repository, with no global ignore file or case folding.

Owner resources must already exist in the producer graph. Current native CONSTRUCT export can otherwise turn an unknown constant IRI into a literal; scope generation checks each resource and refuses unknown or unreadable owners. Register resources through the producer’s governed ingest path before exporting. Generation reads the producer; it does not register owners or change ROOT.

Opt-in hooks retain this mode with init --pack-hooks --monorepo. Cloud setup uses CABOODLE_PACKS_MONOREPO=1 alongside the existing repository and authority settings. Neither mode is armed by default. See the replayable Reckoning demonstration in docs/demos/monorepo-sync/ for the pinned checkout, explicit synthetic CODEOWNERS and isolated stores.

4. Pointers to graphs that are not loaded

A pack’s own manifest can name a graph the pack does not contain, so pointers need no repository-level file either. A repository that ships only pointers ships a pack with no facts.

[[pointer]]
graph = "https://example.org/homelab"
kind = "endpoint"                     # or "pack"
endpoint = "https://quipu.example.org/sparql"
  • kind = "endpoint": a live, large or fast-moving graph, such as a homelab operations graph. caboodle packs sync adds it to quipu’s declared federation list, so SERVICE and federated fan-out reach it with no local copy. The trust rule of section 2 applies to the endpoint: declaring it in a foreign pack does not authorise it.
  • kind = "pack" (with url and sha256): a pinned, offline-capable pack. It is fetched, verified and composed on first use rather than at checkout. This also works in the browser build.

A sparse monorepo checkout can federate to the projects it did not check out this way.

Test cases

CaseCovers
quipu’s own repository plus a homelab operations packsections 1, 2 and 4
scbrown/reckoning (pnpm monorepo)section 3
A pack with a tampered archivethe hash refusal
A foreign repository with no allow entrythe trust refusal
Two commits of one packpointer moves; previous kept; retraction carried

Proposed children

  1. quipu: the per-repository current pointer and snapshot retention, with retraction carry-forward across snapshots of one pack.
  2. caboodle: the .quipu/packs/ convention, caboodle packs sync, and the opt-in git hooks.
  3. caboodle: the trust allow-list for foreign packs and pointer endpoints.
  4. quipu and caboodle: monorepo derived shares by path prefix, proven on scbrown/reckoning.
  5. quipu: pointer entries in the pack manifest, and lazy kind = "pack" pointers.

The stack README standard

Every public repo in the stack (caboodle, quipu, camayoc, bobbin, yupana, desire-path) follows one README shape and one theme, so a reader who knows one can find their way around the others: try it first, then this book for depth.

  • The standard: section order, theme, the shared stack block, the rules, the mdBook skeleton, and the review checklist.
  • The README template: a fill-in-the-blanks README that already meets it.

Status: draft, under review with the yupana pilot before the other repos adopt it.

Docs map

Start here

Understand the design

Documents outside the book

These links point to the source repository so they work on both GitHub and the published book. Every loose document under docs/ is listed here.

  • Stack README standard: section order, theme, shared stack block and review checklist.
  • README template: fill-in template implementing the standard.
  • Design vision: Historical design proposal; use the reference for shipped commands.
  • txtai gap analysis: Historical comparison and design opportunities, not a current capability guarantee.