Vision — governed memory for working agents
Status (2026-08-06): founding document.
The problem
Agent crews accumulate experience and throw it away. Decisions get made, conventions get settled, failures get diagnosed — and the next session starts cold, or worse, starts from a summary an LLM hallucinated about what happened. The stack has a governed bitemporal store (quipu) precisely so knowledge can persist with provenance; what it lacks is a disciplined way for working activity to become governed knowledge.
The temptation is to bolt an extraction pipeline onto the store. TrustGraph and its peers do exactly that, and it works — until you ask "why should I believe this fact?" and the answer is "a model wrote it during ingestion." Quipu's whole posture is that the store never trusts an extractor. So the extraction layer must live outside, with its own discipline, its own tests, and its own repo. This one.
The bet
Three claims, each already proven once in this stack:
- Deterministic-first ingress produces facts worth governing. NeuralAmplifier's datalinks pipeline parses game rules with deliberately no model in the loop, tags every fact with engine/tier/source under SHACL refusal, and the result is a knowledge plane agents can cite without caveats. Camayoc generalizes that pattern beyond one game.
- The graph is the truth; everything else is a projection. Shantytown
already derives agent hierarchy from
reports_toedges by query — "the hierarchy is a query, not a thing to store." Extending that from identity to memory is the same move: store facts true at write time; derive judgments at read time. - Knowledge should ship as artifacts. Quipu's knowledge packs (#79–#82) make a domain — graph, shapes, competency queries, labels, manifest — one attachable, hash-verified file. Camayoc is the producer of those packs.
The first domain: the crew itself
Agentic coding with shantytown is the right first domain because the loop closes entirely within the stack: shantytown emits the work, hank observes the code, camayoc governs activity into quipu, bobbin serves it back into the next agent's context. Memory for the crew, with provenance instead of vibes.
The first vertical slice is the task lifecycle with decisions attached — because "what did we decide about X, and why" is the single highest-value question a crew's memory can answer, and because shantytown's event architecture already learned (the hard way) exactly which facts an event can honestly carry.
The method
Inherited from the ontology-engineering tradition and this stack's own planning docs, and non-negotiable:
- Competency questions before classes. Every ontology change is justified by a question an agent needs answered, and the question set is the test suite.
- Vertical slices before breadth. One workflow modeled end-to-end and validated against real activity, before any widening.
- Grow, don't design. The ontology grows from real agent queries under governance; a comprehensive upfront model would never ship and would be wrong anyway.
- Contested is first-class. Disagreement between sources is recorded as contested, queryable as contested, and resolved explicitly — never silently overwritten by whoever wrote last.
Where it ends up
A shelf of packs: core.pendant.db (the bootstrap vocabulary), crew.pendant.db (the
agentic-coding domain), and eventually others — each versioned, hash-cited,
carrying its own competency queries and recommended trust posture. An agent
team anywhere in the stack attaches the packs it needs and inherits not just
facts but the discipline that produced them.
Getting started
The README's first success, with what each step does and what to do when it does not go to plan.
Bootstrap from a shell
git clone -q --depth 1 https://github.com/scbrown/camayoc
QUIPU_SERVER=http://127.0.0.1:3030 bash camayoc/scripts/bootstrap.sh > bootstrap.log 2>&1
grep -E '^(ontology|shapes|gate: PROVEN — an untagged|camayoc:)' bootstrap.log
scripts/bootstrap.sh runs four idempotent phases, each skipped when it is
already satisfied:
- Reach a quipu, or install one. It writes
.bobbin/config.tomlwithvalidate_on_write = true. If noquipu-serveris on yourPATH, it downloads the latest quipu release (sha256-checked) into.quipu/bin/, or falls back tocargo install quipu-ai. It then starts the server against.quipu/store.db. - Load the ontology and shapes (
ontology/core.ttl,shapes/core.shapes.ttl), register and label the quarantine planes, and load the stored competency queries. - Prove the gate. It sends records that each omit exactly one required property and requires the store to REFUSE every one. A store that accepts one is reported loudly, never ingested into.
- With
--with-claude-hooks, merge the SessionStart status hook into.claude/settings.json(for installs that skip the plugin).
When something fails
| exit | meaning | what to do |
|---|---|---|
| 1 | nothing could be installed (no release for your platform, no cargo) | install a Rust toolchain, or put a quipu-server on your PATH |
| 2 | the store ACCEPTED a record it must refuse | the gate is off: check validate_on_write in .bobbin/config.toml; do not ingest into this store |
| 3 | no verdict could be reached | the server did not answer; read .quipu/server.log |
The server keeps running for your agents. Stop it with
kill "$(cat .quipu/server.pid)".
Using it with agents
The Claude Code plugin
Camayoc ships as a Claude Code plugin. In Claude Code:
/plugin marketplace add scbrown/camayoc
/plugin install camayoc@camayoc
That gets you, immediately:
- The skill — auto-triggers when memory matters; teaches the four moves.
- A SessionStart hook — every session opens knowing the truth about its
memory:
ACTIVE (N facts, gate loaded),reachable but gate not proven, orunreachable— and unreachable is reported as "could not look", never as "nothing exists". /camayoc:bootstrap— from nothing to governed memory, idempotently: if no quipu is reachable it installs one (writes.bobbin/config.tomlwithvalidate_on_write = true, downloads the latest quipu release binary — sha256-checked — or cargo-installs it, starts it against.quipu/store.db, gitignores.quipu/), loads the core ontology + SHACL shapes, registers and labels the quarantine planes (both or neither — see "What runs today" above), and then proves the gate: it sends a deliberately untagged probe and requires the store to refuse it — one probe per refusal arm, each omitting exactly one required property, so a passing probe proves its own shape and nothing else. A store that accepts a probe is reported, loudly, not ingested into.- The ontology and shapes themselves (
ontology/core.ttl,shapes/core.shapes.ttl) — work items, decisions, outcomes, and the mandatorysourceKindprovenance tag.
There is no prerequisite beyond Claude Code itself: the bootstrap brings its
own server, config, and gate — and tells you honestly when it can't. (For
setups that skip the plugin, scripts/bootstrap.sh --with-claude-hooks also
merges the session status hook into .claude/settings.json.)
After the core succeeds, bootstrap offers the rest of the stack — your choice, per component, never assumed:
- bobbin — semantic code search +
context bundles; installed from crates.io, project indexed,
bobbin serveadded to.mcp.json. - Yupana — defs/refs, call graph,
blast radius, and change-time policy checks; installed from git
(pre-release),
yupana serveadded to.mcp.json(hankremains a compatibility alias). - beads — the agent-first
work-item tracker (
bd). Dual role: shantytown's first-class tracker backend, and a deterministic observed-tier ingress path — a bead is aWorkItemrecord camayoc can govern into the graph. - shantytown — the crew
harness; pip-installed, then
st initis left to you — it asks its five questions itself and shows every path before writing.
…and offers to seed the graph's anchors from a codebase + docs (this project, a path, or a git URL): a deterministic, SHACL-gated walk mints the modules, symbols, documents and sections that decisions anchor to — because "what did we decide about X" needs X to exist before it can be answered.
How you use it: the skill is the interface
Camayoc's primary consumer is an agent in a session, so camayoc ships a
skill (skills/camayoc/SKILL.md) that teaches any
agent the four moves: bootstrap a bare store (load ontology + shapes,
prove the SHACL gate is live), query first (ask the competency
questions before re-deciding anything), record at the moment (decisions
as episodes when they happen, not in a wrap-up), and tag honestly
(observed / declared / inferred — never up-tagged). The skill guides;
the shapes enforce — delete the skill and the store is exactly as safe, just
harder to use well. No harness is required: any agent that can speak HTTP to
a quipu can follow it. docs/design/skill.md
First domain: agentic coding. The crew ontology + the task-lifecycle
vertical slice, recorded by skill-guided agents; harness record parsers
(shantytown, git) are optional observed-tier enrichment, never a
dependency.
docs/design/task-lifecycle-slice.md
Reference
Every entry point is a script under scripts/, most with a just recipe.
Each takes --help.
| command | what it does |
|---|---|
scripts/bootstrap.sh [--with-claude-hooks] | reach or install a quipu, load ontology + shapes, prove the gate |
scripts/competency.py "<question>" | is a question covered by the competency suite? (--map asks Jev and returns a stored query) |
scripts/competency.py --list | the whole suite, with its watermark |
scripts/query_coverage.py | which competency questions have a stored query that answers them |
scripts/planes.py status | the quarantine planes, and whether each is labelled |
scripts/settled_decisions.py "<proposal>" --declared <file> | does a proposal collide with a decision already made? |
scripts/jev.py noul|choice|score ... | ask Jev for one typed decision (needs a TypeSafe key) |
scripts/jev_mcp.py | the same, as an MCP server (declared in the plugin) |
scripts/backfill_work_items.py | every tracker record as a WorkItem, paced and resumable |
just check / just test | the local quality gate and the test suite |
The competency questions themselves live in
competency/; the
stored queries that answer them live in
queries/.
Governance resources
Camayoc provides an explicit offline stage for the governance catalogue and combined SHACL profile. It writes a new owned scratch database only after native pendant verification and exact catalogue/profile readback. It does not load a running service, replace an existing catalogue, install a native policy adapter or activate a hook.
See the resource guide for required identity arguments, repeat behavior and refusal cases. The consumer contract defines the separate projection, cache, exemption, freshness and atomic refresh requirements for a future native adapter. Offline stage success does not prove that adapter is implemented or independently certified.
Tests generate a fresh local pendant from the reviewed resource bytes, verify 449 declarations and the complete profile, and exercise an isolated server: unknown types and invalid mixed transactions leave every submitted subject absent; a valid proposed AdmissionPolicy is the positive writer control.
Source releases
Camayoc releases contain the tracked source tree used by the plugin and shell bootstrap, plus deterministic compatibility files built from that archived source. They do not contain a native Camayoc executable.
A stable version tag matching both plugin metadata files produces three assets:
camayoc-vMAJOR.MINOR.PATCH.tar.gz: tracked source under one enclosing directory.SHA256SUMS: the archive's SHA-256 digest and exact filename.source.json: repository, tag, full source revision, artifact name and digest.
Download all three from the selected release and verify before extracting:
sha256sum -c SHA256SUMS
tar -xzf camayoc-v0.1.0.tar.gz
The checksum establishes agreement with the published asset. Automated consumers must also pin the independently reviewed digest and revision; a checksum fetched from the same release is not an independent source of trust.
The Release workflow accepts an existing stable tag on main, either from its tag push or an explicit dispatch. Before publication, the shared source-release proof runs lint, private-key checks, the full test suite and packaging controls. It then extracts the actual archive and runs bootstrap, repeat-bootstrap and ingest/refusal controls against disposable loopback Quipu using the same reviewed dependency as the existing integration job. A missing dependency or failed proof stops publication. The same proof runs on packaging pull requests without publication permissions.
Packaging reads an exact Git revision. Untracked files and modified checkout bytes never enter the bundle. Stable versions must agree with both metadata files, and the output directory must be empty. Repeating the same revision with the workflow's Python 3.11 runtime produces the same archive bytes. Existing releases are not overwritten. Publishing a source bundle does not install it or activate a service.
Domain compatibility artifact
When the archived revision carries the pinned original domain ontology and its
compiler, the same producer adds compatibility/aegis/ inside the existing source
tarball. Its manifest.json names every per-domain file, SHA256, byte count and
shape count. source.json adds a compatibility descriptor with that manifest's
path and SHA256. No extra release, publisher or automatic loader is introduced.
Older revisions without those inputs retain their original source-only format.
The build uses only archived Git objects, including the frozen upstream fixture; dirty working files cannot alter its inputs. The compiler requires RDFLib 7.6.0, restores original target IRIs and proves the independently parsed serialized union RDF-isomorphic to the pinned upstream graph: 120 NodeShapes. Existing legacy aliases and rule/property exclusions are not changed by packaging.
A consumer must independently pin the normal archive digest, full source revision and compatibility manifest digest, verify regular members and all carried file hashes, then use its normal scoped loader and rollback. A successful package does not prove live loaded counts, current-data validation, alias-query equivalence or removal of the old source copies. No tag, release or installed pin is selected by this source change.
Why camayoc
The long form that used to open the README: its place in the stack, what this repository owns, what runs today, what it deliberately is not, and the name. The README now leads with a three-command first success (the caboodle-stack README standard).
Role in the stack
shantytown / git / yupana / sessions (activity: raw fiber)
│
▼
camayoc (ontology + ingress + packs)
│ episodes, SHACL-tagged, tiered
▼
quipu (governed bitemporal store)
│
▼
bobbin / agents (retrieval, context, RAG)
- quipu stores and governs; it deliberately contains no extraction and no LLM.
- Yupana observes code structure. The
hankexecutable name remains only as a compatibility alias. - bobbin serves knowledge back into agent context.
- shantytown runs the crew whose activity is the first knowledge domain.
- camayoc is the layer TrustGraph-style platforms put inside the store and this stack deliberately keeps outside it: ontology, ingress discipline, and distribution.
What this repo owns
- The bootstrap ontology — the small, brutally curated upper vocabulary every domain imports: work, agents, decisions, outcomes, source tiers. Competency questions before classes, always. docs/design/bootstrap-ontology.md
- The ingress discipline — how facts enter quipu: episode-shaped, SHACL-refused without provenance tags, deterministic-first, LLM-inferred knowledge quarantined into low-trust planes. docs/design/ingress.md The declarative structured-source lane is specified as a governed RML/R2RML subset in docs/design/rml-executor.md.
- Domain ontologies as certified knowledge packs — each domain ships as
Quipu's
.pendant.db(quipu #81): graph + shapes + competency queries + labels + manifest, one attachable file. A Camayoc pack is theCertifiedShareBundle, not a second format; publisher and certifier claims bind independently to the manifest content hash. docs/design/certified-knowledge-packs.md - The competency-question suites — the questions agents actually ask, maintained as the test harness for every ontology change. Six slices now: task lifecycle, metrics, verification-and-liveness (with its §D cost accounting), golden paths, document structure and chunks, and workflow-and-archive. competency/
Metrics and nonfunctional requirements
Camayoc catalogues metric definitions and executable retrieval methods; it does not copy time-series samples into Quipu. Prometheus rule ingress is a reconciled producer snapshot, so changing or removing a rule converges the catalogue without appending duplicate episode comments:
scripts/reconcile_metrics.sh /path/to/prometheus/rules https://prometheus.example
The first argument may be one rule file or a directory containing YAML/Jinja
rule templates. The optional endpoint is stored as a retrieval parameter; when
it is absent, retrieval reports unreachable instead of inventing a default.
The parser requires Python and PyYAML. scripts/retrieve_metric.py executes a
method returned by the camayoc_metric_retrieval_method named query and prints
retrieved, unreachable, query_error, or unsupported; it never persists
the returned sample. Authenticated Prometheus deployments are read from
PROMETHEUS_BASIC_AUTH_USER and PROMETHEUS_BASIC_AUTH_PASSWORD; credentials
are never stored in the graph method.
Typed decisions with Jev
Some ingress questions are semantic judgments, not parser facts: which
competency question is this asked question an instance of, if any? Does this new decision
collide with a settled one? Camayoc's first answer to those was deliberately
lexical, and every verdict said so (method: lexical-jaccard-v1,
semantic: false). Since 2026-09-21 there is a second arm:
Jev, TypeSafe's "System One" model, which returns typed,
calibrated decisions (noul yes/no probabilities, choice over up to 255
options, score over ordered levels) instead of text, in one parallel pass.
scripts/jev.py is the only place camayoc calls it. What that unlocked:
- Question mapping as one typed decision.
competency.py --method jevposes a singlechoiceper asked question over the whole competency suite plus a reservednone-of-theseoption, and reads Jev's probability per question. Paraphrases the word-overlap scorer could not resolve now map, with a confidence attached. (Whether a competency question is covered by a stored query is the separate, deterministic check inquery_coverage.py; Jev never answers that.) - An abstention Jev does not have. Jev cannot say "nothing fits"; the
reserved option is how a forced choice becomes an honest NO COVERAGE.
When it wins, coverage is
Emptywhatever the runner-up scored. - The same honesty rules as before. The verdict carries
method: jev-latest-choice-v1,semantic: true, the instructions posed, the model that answered, the confidence and the none probability. No key means a loudJevUnavailable, never a quiet fall back to word overlap under a Jev label. - Ingress discipline unchanged. Every Jev answer is a model judgment. If it
is written back it is
inferredand lands in the quarantine plane; Jev never promotes, and plane routing stays deterministic.
export TYPESAFE_API_KEY=... # from your secret store
python3 scripts/competency.py --method jev "which metrics can we retrieve for the database host right now?"
python3 scripts/competency.py --method lexical "which metrics can we retrieve for the database host right now?"
python3 scripts/jev.py noul --state "..." --ask "Does this message request a refund?" --dry-run
Design, candidate slots and the caveats (no rationale, forced choice, context
rot): docs/design/jev-typed-decisions.md.
The settled-decision collision check (noul) is the next arm.
What runs today
The ingress discipline stopped being a table in a design doc. The pieces below are implemented, and — the part this repo cares most about — their refusals are tested, not just their acceptances. docs/design/implemented-set.md keeps the measured, claim-by-claim ledger.
- Quarantine planes (
scripts/planes.py) — writes route bysourceKindinto named graphs labelled in quipu's trust lattice:inferrednever shares a plane withobservedand always ranks strictly below it. Registration and labelling happen together or not at all; an unknownsourceKindrefuses instead of defaulting to ROOT; and against a quipu without the/graph/create+/graph/labelroutes, bootstrap fails rather than quietly writing everything into ROOT. - Two-dimensional routing —
plane_for(source_kind, data_kind).knowledgegoes to the static planes;operationaldata (workflow runs, shuttle's export) goes to time-windowed graphs (scripts/windows.py,{WINDOW_NS}{family}/{YYYY-MM}) so a completed window can be deep-frozen whole. Unknown pairs refuse; nothing defaults to ROOT. - Plane promotion (
scripts/promote_plane.py) — how a fact earns its way out of quarantine. Authority-gated and failing closed (a missing or unreadable grant file means nobody may promote, not everybody), self-promotion refused independently of authority, upward moves only, and the move rule in full: assert in the target, close the source episode (a bitemporal close, never a delete), record the move — withcamayoc:sourceLeftOpen truesaid out loud when the source stays open. - The workflow slice —
WorkflowDefinition,WorkflowStep,WorkflowRun, and append-onlyTransitionEvents;currentStateis re-asserted per transition, never mutated. Every term is owed to a question in competency/workflow-and-archive.md. docs/design/workflow-and-archive.md - Golden paths — verified trajectories, blessed and enforced:
Trajectory,GoldenPath,PathOmission,PathPromotion, with SHACL refusals where absence would make the node a lie (aGoldenPathwithout its exemplar trajectory is refused) and a gate-probe arm proving each refusal. docs/design/golden-paths.md - Stored queries, honest coverage — every named query in
queries/ is tested against a seeded fixture with a positive
and a control-negative arm.
just query-coverageis the living figure: 40 of 91 stored across six slices, plus 22 questions that are expressible with today's vocabulary and simply unwritten, and 29 that are competency gaps. Those last two are reported apart on purpose — an unwritten query is work with a known shape, an ontology gap is a finding — and a question with no stored query is never answered from the nearest term. - Cost accounting —
SessionandUsageRecord, with six §D queries: token cost per work item, per-provider burn windows, sessions with no usage records, what a decision cost. No quota term exists on purpose — the consumption is ours to record; the ceiling is the provider's. - Refused-write denominator (
just refusal-rate) — joins quipu's durablewrite.refusedevent stream to the acceptedVerificationpopulation and reports the refusal share the incident corpus never had. It reports and never writes: a refusal rate is a ratio between two moving populations, so it is a judgment computed at read time, not a fact true at write time. The share is a FLOOR three times over and the report says so on every run —speculaterefusals are excluded from the stream, refused fact bodies are not stored (so no per-form breakdown is recoverable, ever, andreasonnames the gate rather than the failing shape), and a prospective stream divided by a retrospective population has a denominator that is too large. An unreachable store, or one predating the stream, exits 3: could not look is not zero. - git → work-item provenance (
just ingest-git) — walks commit history and emits theWorkItem ←implements— GitCommit —modifies→ CodeModulechain as Turtle. Deterministic, byte-identical on re-run, and pureobserved. It abstains unless you declare the tracker prefix (--project), because no pattern separates a work-item id from ordinary hyphenated English, and a false match silently widens an item's scope. - Competency assessment (
just competency "<question>") — scores a question against the suite and returnsEmpty | Partial | Full, with NO COVERAGE as a first-class verdict. Every verdict carries its method, thresholds, and suite watermark; the embedding scorer is wired and selects itself only when weights are actually present, so a verdict can never claimsemantic: trueover a word-overlap number. - Quarantined entity extraction (
scripts/extract_entities.py) — finds entity mentions in markdown prose deterministically: a gazetteer built from the graph's own labels plus explicit user-declared regex patterns, no model in the loop. Deterministic does not meanobserved— a mention is a reading of the text, so every fact lands in the inferred plane, tagged, never ROOT. Ambiguous labels match for nobody, re-runs are byte-identical, and an unreachable store or unprovisioned plane refuses loudly before anything is written. - Settled-decision collision check (
scripts/settled_decisions.py) — scores a proposed decision against the standing human decisions and surfaces likely re-litigation before the write. Advisory, lexical and says so; its own recorded verdict routes to the inferred plane, because a machine's opinion about a human's decision must not sit beside it looking like one. - Advisory chunk shapes — vocabulary for bobbin's chunk graph
(
bobbin:Chunk,nextChunk,chunkOrder) inshapes/code-entities.ttl, value constraints only until the emitter ships and is measured. - Certified-pack producer boundary (
scripts/certify_pack.py) — invokes Quipu's.pendant.dbpack and verify path, reads the manifest content hash, and renders distinct publisher/certifier claims plus the governed source mapping. It verifies two distinct Ed25519 keys, derives the hash from a conforming machine-readable SHACL report, and scans the complete pack artifact before asserting scrub success. It atomically publishes a content-addressed durable copy and binds that file URI into the source mapping. Static and frozen-window packs are exercised through relocation with disposable independent keys. Quipu's v1 share directory is the git/import projection of this same governed bundle: itsshare_idtracks lineage and never replaces the pack's canonical graph hash. - Governed RML executor (
scripts/rml_executor.py) — compiles standard RML/R2RML triples-map data, refuses unsupported constructs before source access, reads bounded JSON/CSV/SQLite sources, emits sorted duplicate-free N-Quads, and commits through Quipu's SHACL-governed named-graph write lane.
What this repo is deliberately NOT
- Not a store. Quipu is the store. Camayoc never holds truth; it prepares and certifies it.
- Not a harness. Shantytown runs agents. Camayoc only defines what their activity means.
- Not retrieval. Bobbin serves context. Camayoc ships the queries, not the serving.
- Not an extraction platform. Deterministic parsers first; where an LLM infers, the output is labelled as inference and can never masquerade as observation. The tag is the reader's only signal of trust — that posture is inherited from NeuralAmplifier's datalinks pipeline, the proven instance of this pattern.
On the name
The name honors the khipukamayuq (hispanicized quipucamayoc): the Quechua title for the specialists of the Inca state charged with making, maintaining, archiving, and interpreting the khipus — and answerable, personally, for what the knots were read to claim. Kamayuq on its own means "specialist, keeper, one who is charged with": Quechua formed many such titles (punku kamayuq, doorkeeper; chaka kamayuq, bridge keeper). Beside a sibling project named quipu, this repo's name completes the compound.
This project borrows the role as a metaphor for a software layer that decides what a fact may claim before it enters a knowledge graph. It does not claim to represent Andean culture. Khipu is a living tradition — Quechua is spoken by millions, community cord-keeping survived into the modern era in Andean villages, and khipu scholarship is active — and the modern Quechua spellings are khipu and khipukamayuq; this stack uses the older hispanicized forms for continuity with its sibling repos.
The logo's centerpiece is the yupana, the Andean counting board drawn at the khipukamayuq's side in Guaman Poma de Ayala's 1615 illustration of the contador mayor — the only primary-source depiction of one: five rows of cells holding five, three, two and one counters. It was the surface where a value was worked out before being committed to the knots — which is exactly this repo's job, so the keeper's own instrument sits at the center, between the loose thread coming in and the knotted record going out.
Status
Working ingress, honestly bounded. Of the eight aspects in the provisional
disclosure, four are built, four are partial, and none are design-only —
docs/design/implemented-set.md is the
measured ledger, re-run per row rather than carried forward on trust. The
competency suite spans six slices; 40 stored queries answer it where the
vocabulary exists, and just query-coverage reports the remaining gaps as
gaps. The quipu substrate this builds on is itself still in flight.
The stack
Camayoc is the layer between raw activity and the governed store: it owns what the knots mean and how knowledge earns its way into the graph.
- quipu stores and governs, and holds no extraction and no LLM.
- bobbin serves knowledge back into agent context.
- yupana observes code structure.
- desire-path records the tool calls agents get wrong.
- caboodle installs all of them together and proves each one works.
The diagram and the division of labour are on Why camayoc.
Metrics: functional counters without a server
camayoc is a CLI toolkit. Every entry point in the justfile is
python3 scripts/<x>.py, nothing listens, and the service name it would answer on
returns 404 because there is no service behind it. That is not a gap to be closed by adding a daemon —
it is what camayoc is.
So the work reports itself as it happens, to a Pushgateway. This is creel's
resolution to the same problem (creel/docs/metrics.md), and the shape
aegis-wou8k asks for.
What is exported
| job | pushed | carries |
|---|---|---|
camayoc_producer | every run, unconditionally | camayoc_producer_run_timestamp_seconds, camayoc_producer_duration_seconds, camayoc_producer_exit_status |
camayoc | only on a successful run | the functional samples below |
| metric | moves when |
|---|---|
camayoc_ingest_commits_scanned{adapter} | an ingest reads more history |
camayoc_ingest_items_linked{adapter} | commits carry work-item ids |
camayoc_ingest_commits_unlinked{adapter} | they do not — the denominator |
camayoc_ingest_edges_written{adapter} | modifies edges are produced |
Why two groups
A gap in a pushed series has two causes that are identical at the gateway:
the producer DIED vs the producer RAN and had nothing to say
camayoc_producer going stale means nothing is running it. camayoc going stale
while the producer group stays fresh means camayoc ran and did no work. Collapse
them into one job and a pipeline can go quiet while everything reports healthy —
which is the up=1-while-dead class this exists to close, and which this fleet
has been bitten by (a watcher sat up=1 for three hours observing a store nobody
was writing to).
Each adapter (and optional instance) has its own grouping key. Pushes use PUT to replace that complete group: a metric name omitted from the next successful run disappears. POST would retain that retired name indefinitely. The producer and sample jobs are separate, so updating one leaves the other untouched. A failed run updates only the producer group and retains the last good samples.
Why these counters and not up
up 1 says a scrape succeeded. It is true of a tool that has done nothing for a
week. A functional counter moves when real work happens and stays put when it
does not, so the difference between two readings IS the work. The numbers above
are the ingest's own totals — the same ones it prints for a human — pushed
unchanged. Counting separately from the work lets the count drift from it, and
then the metric is measuring the metric.
Running it
CAMAYOC_METRICS_PUSHGATEWAY=http://user@host[:port] \
CAMAYOC_METRICS_PASSWORD_FILE=/path/to/password-file \
python3 scripts/ingest_git_provenance.py <repo> --project <prefix>
Unset the variable and nothing is pushed — the ingest is unaffected and says
metrics: CAMAYOC_METRICS_PUSHGATEWAY is unset on stderr. That direction is
deliberate: the ingest is the point and the metric is the observation of it, so a
missing gateway must never fail a run. It is never SILENT about it either, because
a metrics pipeline that quietly does nothing is the thing being guarded against.
The password file takes precedence over an inline URL password. An unreadable or empty configured file refuses the push without falling back to the inline credential. The URL must include a username when using a password file. Without the file setting, inline credentials and unauthenticated gateways still work. The credential arrives at run time and is never written to the repo.
Verified
2026-09-04, two runs against a live gateway, same command, different --limit:
| run 1 | run 2 | |
|---|---|---|
camayoc_ingest_commits_scanned | 5 | 40 |
camayoc_ingest_items_linked | 1 | 15 |
camayoc_ingest_edges_written | 2 | 76 |
read back from Prometheus, not from the pushing script:
camayoc_ingest_commits_scanned{adapter="git_provenance", job="camayoc"} => 40
camayoc_producer_exit_status{adapter="git_provenance", job="camayoc_producer"} => 0
A moving value read from the scrape target is the claim; a push returning 200 is not, and neither is a gauge that is always 0.
2026-09-11, isolated adapter groups on a live gateway: PUT of A+B followed by A
left A present and B absent. The POST control retained B after the same pair of
pushes. The sibling producer group's scraped lines stayed byte-identical in
both arms. Both probe groups were deleted and read back as absent. The PUT arm
used push() with a password file and a username-only gateway URL.
Design: Ingress — how knowledge earns its way into the graph
Implementation status (2026-08-07): 🟨 Core ingress gate built: WorkItem, Decision, Verification, ExecutionPath, and Blocker provenance/validation shapes are present. Bootstrap proves both an untagged-write refusal and a falsifier-less Verification refusal. Execution-path inventory is now produced by the scheduled, ownership-neutral aegis source and the Quipu coverage query reports
Empty,Partial, orFullrather than a bare count. Hank now emits a falsifier-gatedVerificationand abuiltBlockerfor each signed unsatisfied verdict. Its source change is merged, but it is not yet an installed/runtime producer: the sanctioned installer consumes release artifacts and no verdict-drain schedule is present. Until both are true, the blocker query's zero is an explicit gap, not evidence that no blocker exists. Rule 1 corrected (2026-08-16): it claimed episode-shaped ingress; every real ingest is and always was a/knotwrite, so the rule now states the boundary it actually governs andtests/test_knot_provenance.pyenforces theactor+sourcefloor it narrowed to (camayoc-99t). Planes update (2026-08-18, noted here 2026-08-22): the §2 quarantine planes are no longer a table —scripts/planes.pyregisters and labels them (camayoc-s0h, see §2.1) andscripts/promote_plane.pyimplements the authority-gated plane promotion of §3.4/§4 (camayoc-mip) — assertion into the target only; the retraction half awaits a keeper ruling,camayoc-913. Routing update (2026-08-24): routing is now 2-D —plane_for(source_kind, data_kind="knowledge");operationaldata is window-addressed viascripts/windows.pyso completed windows can be deep-frozen whole, and §4's promotion now CLOSES the source episode (the camayoc-913 move rule). Existing plane IRIs untouched. See workflow-and-archive.md.
1. The discipline, in five rules
-
Episode-shaped writes for agent-recorded facts. Everything the skill records in the moment — decisions, work items, outcomes — goes through quipu's
/episodepath, which suppliesprov:wasGeneratedByautomatically and is idempotent (branch onoutcome, never oncount). That is where the provenance chain must be unskippable: those facts have an author, a moment and a motive, and a later reader has to be able to weigh them.Bulk deterministic loads go through
POST /knot, and this rule does not pretend otherwise. The ontology and shapes (scripts/bootstrap.sh), the repository walk (scripts/seed_knowledge.sh) and the metric catalogue (scripts/reconcile_metrics.sh) are free-Turtle knot writes. They carryactorandsourcein the payload instead of a PROV activity;prov:wasGeneratedByappears nowhere in the codebase outside these design docs.This rule previously read "every camayoc ingress goes through
/episode… no free-Turtle writes to governed planes", which was false in every particular: the only/episodecalls in the repository are the four deliberately-invalid gate probes inscripts/gate_probe.sh, and every successful ingest has always been a knot write (camayoc-99t).What the narrowing costs.
actor+sourceis a weaker record than a PROV activity: it names who wrote and what they read, but not the run, and it is not chained, so a knot-written fact cannot be traced to the invocation that produced it. That is an acceptable trade for the ontology and shapes — schema, not claims — and a real gap for the seed walk and the metric catalogue, which are claims about a codebase.tests/test_knot_provenance.pyholds the narrowed rule to its own terms: a knot write that names neither its actor nor its source fails the suite, so the weaker record cannot quietly become no record. -
SHACL refuses the untagged. Every fact carries
camayoc:sourceKind(observed|declared|inferred) and a source reference. Closed vocabulary,minCount 1, from the first load. This is NeuralAmplifier's proven posture (its ingest literally refuses a mod file claiming to be canonical), generalized. -
Deterministic-first. If a parser can produce the fact from a record, the parser does — no model in the loop on
observedfacts, because a hallucinated fact is indistinguishable downstream from a real one on exactly the facts tagged trustworthy. -
Inference is quarantined, not banned. LLM-derived knowledge (session summaries, "lessons learned," failure diagnoses) is valuable — it lands in its own plane (named graph) labelled low-trust in the lattice, so the label meets through every query that touches it. It can earn promotion (a graph move, bitemporal and auditable) — it can never masquerade.
-
Facts true at write time. No stored judgment that decays (liveness, currency, "still in progress"). Judgments are queries at read time.
-
Verification names its falsifier. A
Verificationcarries the observable result that would have disproved it. The shape refuses a claim without one; bootstrap proves that refusal separately from the provenance-tag gate. -
Liveness is calculated, never written. Producers store timestamped observations (a stop record, a refresh, an installed-artifact hash). A reader combines them with the current process or artifact state.
ExecutionPathdeliberately has noisLiveproperty: a stored verdict would age into the stale fact it claims to detect. -
Blockers name their evidence strength. A
Blockeris exactly one ofstated(a claim in a ticket/report) orbuilt(a demonstrated failing case). When a built blocker linksdemonstratedBy, it must link a falsifier-gatedVerification; Hank emits that link for signed unsatisfied verdicts. A reader can therefore keep a stated blocker visible without presenting it as a measurement. Absence of such a record remains a producer coverage question, not a claim that no blocker exists.
2. Planes by velocity
Mirroring the pattern NeuralAmplifier proved (datalinks / doctrine / memory), mapped onto quipu named graphs with lattice labels:
| Plane (named graph) | Contents | Velocity | sourceKind | Trust label |
|---|---|---|---|---|
crew:records | task lifecycle, decisions, outcomes (skill-recorded or parser-backfilled) | per-event | observed / declared | high |
crew:declared | conventions, directives, standing decisions a human stated | occasional | declared | high (human root) |
crew:inferred | model-written summaries, diagnoses, lessons | per-session | inferred | low, promotable |
code:* | hank-promoted structure (already governed upstream) | per-commit | observed | per hank tier |
2.1 Implementation status: routed and labelled (camayoc-s0h)
Update 2026-08-18. This section described a design. It now describes a deployment:
scripts/planes.pyregisters each plane and labels it in the trust lattice,scripts/bootstrap.shruns it, and writes are routed to the plane theirsourceKindearns. The two quipu gaps below were closed upstream on the same branch —POST /graph/createandPOST /graph/labelexposeStore::graph_createandStore::set_graph_label, both of which already existed with no way in from outside.The ordering held. Registration and labelling happen together or not at all, and
plane_forrefuses an unknownsourceKindrather than defaulting to ROOT. Against a quipu without the routes, bootstrap fails — it does not quietly carry on writing everything into ROOT, which is the "looks implemented, does nothing" state this section was written to warn about.tests/test_planes.pypins that refusal, and pins thatinferrednever shares a plane withobservedand always ranks strictly below it.Writes go through
/episode, not/knot, for the reason recorded below:/knotis hardcoded to ROOT and silently drops agraphkey.The historical analysis is kept below because its reasoning is why the ordering is what it is.
Nothing routed at the time of writing. No script names a graph, no shape targets one, no query
carries a GRAPH clause. An inferred-tagged node lands in exactly the same
place an observed one does, so quarantine is skill discipline and a sourceKind
string — not an enforced boundary (camayoc-s0h).
Read against quipu's source rather than assumed, because the shape of the gap decides who fixes it:
| Capability | State in quipu | Citation |
|---|---|---|
Quad store, GRAPH / FROM NAMED in SPARQL | exists | src/store/migrate.rs:33, src/sparql/pattern.rs:352-372 |
Write into a named graph via /episode | exists (graph field) | src/episode/mod.rs:177-181, test at src/episode/tests.rs:912 |
Write into a named graph via /knot | does not exist | hardcoded to ROOT, src/rdf.rs:180-182 |
| Trust lattice, labels, query-time floors | exists | src/lattice.rs, src/store/labels.rs:730 |
| HTTP or CLI route to set a graph label | does not exist | only caller of set_graph_label is src/pack.rs:450 |
Labelling a graph /episode created | refused — the write interns the IRI without registering it | src/store/labels.rs:344-350 vs src/episode/mod.rs:371 |
| Store-level primitive that registers a graph | exists, with no caller | Store::graph_create, src/store/overlays.rs:42 |
So routing is blocked in a specific and fixable way rather than generally:
- Every real camayoc ingest is a
/knotwrite (rule 1), and/knotcannot target a graph. Worse, it takes a free-form JSON body with no unknown-field rejection, so a"graph"key added to a knot payload is silently dropped — routing would appear to be implemented and do nothing.tests/test_knot_provenance.pypins that no knot payload carries one. - Labels have no write route at all. The whole lattice — composition,
per-row annotation, query-time floors — is built and reachable only from
Rust. A plane camayoc created could not be labelled
low-trusteven by hand.
What unblocks this, in order. Re-verified 2026-08-17, and the estimate is smaller than it looks, because the missing pieces are exposure rather than mechanism:
Store::graph_create(src/store/overlays.rs:42) is exactly the registrationset_graph_labelpresumes — written, tested, idempotent, and refusing a class change. It has no caller outside its own tests.Store::set_graph_label(src/store/labels.rs:208) is likewise built and reachable only from Rust, its single production caller beingsrc/pack.rs:450.
So quipu does not need two mechanisms built; it needs graph_create invoked
when /episode is handed a graph it has not seen (or a route that does it),
and an HTTP route over set_graph_label. Camayoc then moves agent-recorded
facts to /episode with graph derived from sourceKind — and note that
/episode's graph field already works, so camayoc's half is not blocked
on routing at all, only on the labels.
Until the label route exists, routing alone would produce separate graphs that every query still reads at equal trust — the appearance of quarantine without the substance, and worse than none. That ordering, not the missing routing, is why this is not partially shipped, and it is worth being precise about: camayoc could route today and must not.
This is a built blocker in the sense of §1.8, not a stated one: the
citations above are the demonstration.
3. Sources
3.1 The skill-guided agent — the first-class source
The primary ingress surface is the agent itself, guided by the shipped
skill (skill.md, skills/camayoc/SKILL.md): it records
decisions, work items and outcomes as tagged episodes at the moment they
happen. Guidance and enforcement are deliberately separate — the skill makes
the honest tag the path of least resistance; the shapes refuse what the
skill failed to prevent. No harness is a dependency: any agent with HTTP
reaches the whole surface.
3.2 Tracker and harness record parsers — optional enrichment
A tracker or harness with its own records can be backfilled and corroborated
by a deterministic parser translating records → episodes tagged observed:
Implemented for beads/st: scripts/ingest_work_items.py. It consumes the
public br show <id> --json record, reuses the existing WorkItem class, and
routes an ordinary governed episode to crew:records. The stable tracker id is
both the node name and aegis:identifier, so lookup works from record to graph
and from graph back to record. --about <IRI> records explicit WorkItem →
Directive/entity mappings without minting a tracker-specific class. Quipu owns
none of this translation: it only stores the episode Camayoc produces. Mutable
record versions are immutable Observation nodes keyed by a content digest;
the WorkItem projection is byte-identical on every run. A failed record is
printed as outcome=parked with exit 2 so batch callers can continue instead
of head-of-line blocking the lane.
Each tracker Observation also points about its WorkItem. The incoming
WorkItem observes Observation edge does not satisfy the Observation shape's
required outgoing about property. Fresh-bootstrap integration tests exercise
the complete installer, accept a real tracker episode, read the edge back from
its named graph, and reject an otherwise identical unanchored Observation.
Testing only the serializer or a store without write-time validation misses
this contract.
- Beads (
br) is the cleanest such source: agent-first, JSON out, and a bead is aWorkItemrecord — id, status, dependencies, lifecycle — so the parse is nearly a projection. Where beads is st's tracker backend, one parser covers both. - Shantytown's own records (task/crew, with the
ts/item/item_statuspayload its event store already learned an event must carry) parse the same way. Shantytown's adapter table reserves aknowledgelayer for a native emit half; that is a welcome integration on that repo's schedule — camayoc works identically without it, and its none-adapter proves the independence in the other direction.
3.3 Git
Implemented: scripts/ingest_git_provenance.py. Commit history →
work-item linkage (aegis:implements, aegis:modifies — the provenance chain
quipu's co-occurrence shapes already define). Pure observed; yupana promotes
the structural half.
Work-item targets use the bare tracker ID in the ontology namespace, matching
ingest_work_items.py. Provenance emits only the implements edge to that
WorkItem: its type, label, identifier and trust facts belong to tracker records
and are not copied here. Readers join the records and provenance graphs.
Code modules and commits retain their code namespace.
Older output used code/bead/<id> for work items; those historical aliases need
explicit reconciliation, not a second mint or deletion of their provenance.
scripts/ingest_git_provenance.py ../quipu ../yupana --project aegis > /tmp/prov.ttl
# then POST it to /knot like any other ingest — see scripts/bootstrap.sh
This chain is read by more consumers than any other thing this repo emits, and
none of them owns it: yupana's WORK_ITEM_SCOPE_QUERY builds the observed
rung of its capability ladder from it, its work-item briefing reads it for an
item's ground, and quipu's entity_work / cochanged_with / cooccurrence
traverse it. A repository this has never run against produces UNKNOWN scope for
every agent working in it — correctly, and uselessly.
The project prefix must be declared (--project, defaulting to the repo
names). There is no pattern that separates bobbin-052 from ordinary
hyphenated English, and this was measured rather than reasoned: the first
version used a loose regex and read work-item, advise-mode, authored-by,
pre-push and force-push as work-item ids, writing paths into the ground of
five items that do not exist. A digit test does not rescue it either —
bobbin-bbe is a real id. So the ingest abstains unless somebody named the
project, and tests/test_ingest_git_provenance.py holds it to that.
Coverage is reported, not assumed. The runner prints how many commits carried a recognised id and how many did not. A ground built from a history where 80% of commits are unlinked is a floor, and whoever reads that scope needs to know it — a linked-only count would let an ingest matching 5% of commits look like one matching 95%.
3.4 Sessions (the inferred plane)
Transcript-derived summaries and diagnoses, written by a model, landing in
crew:inferred only. The interesting future here is the SARC trust-predicate
gap: a producer that records sub-agent responses is exactly what a session
adapter is — but v1 claims only quarantine, not evaluation.
3.5 Prose entity mentions (deterministic, still quarantined)
Implemented: scripts/extract_entities.py (camayoc-0c8). Markdown prose →
entity mentions, matched against a gazetteer of the graph's own labels plus
explicit user-declared regex patterns — no model in the loop. Deterministic
extraction does not make the claim observed: "this prose is about that
entity" is a reading, so every mention routes to crew:inferred and never
ROOT. Ambiguous labels match for nobody, abstentions are counted on stderr,
and an unreachable store or unprovisioned plane refuses before a triple
moves. See entity-mentions.md.
4. What ingress never does
- Never writes to a governed plane without the tags (SHACL enforces).
- Never upgrades its own output: promotion out of
crew:inferredis a governed graph move requiring authority over the target plane, not an ingress feature. Implemented asscripts/promote_plane.py(camayoc-mip). Called plane promotion throughout, becauseshapes/code-entities.ttlalready uses "promotion" for an unrelated bobbin-to-quipu ingest path and two governed operations sharing one word in one repo confuses every reader after the one who wrote it. Four gates, all refusing before anything is written: no authority over the target plane; self-promotion, checked independently of authority so a principal holding a grant still cannot launder its own output through it; a sideways or downward move, which is a category error that would read in the record as earned trust; and an unstated reason. Grants live inconfig/plane-authority.json, are human-maintained, and fail closed — a missing or malformed file grants nobody, because treating an unreadable grant file as unrestricted is what makes an authority check worthless. The promotion event is itself taggedobservedand carries its own falsifier: the move is an observed event even though the fact it moves was inferred, and tagging itinferredwould file the audit record in the plane it is moving things out of. - Never stores a judgment that decays (§1.5).
- Never models the tracker's message format — the record is evidence about the work, not the shape of the work.
5. Related
- bootstrap-ontology.md — the vocabulary these writes carry.
- skill.md — the skill-as-interface usage model this section implements.
- task-lifecycle-slice.md — the first slice; its record mapping is the enrichment parser's translation table.
- shantytown
docs/adapters.md— the reserved knowledge seam and the two-implementations rule;shantytown/events.py— the event-payload lessons §1.5 inherits. - quipu
docs/design/graph-labels.md— the lattice that makes quarantine enforceable rather than aspirational.
WorkItem graph scope and historical identities
An unqualified direct-type query such as ?item a ?type, even with a
WorkItem type filter, reads the default graph. It does not establish
presence or absence in the tracker records graph. The default graph retains
historical WorkItems; use the explicitly authorized records graph for tracker
evidence. Do not interpret a large default-graph answer as current tracker
coverage.
The original September 2026 population counts are historical measurements, not a maintained census. A bounded October follow-up found current identifiers in records but absent from default, and an older identifier present in both. The populations must not be assumed disjoint, and identifier length is not a routing rule. Direct typing proves presence in a scope; it does not establish current status, the latest tracker revision, or completeness.
Keep the two identity questions separate. Canonical tracker identities use the
bare tracker ID, as described above; historical code/bead/<id> provenance
aliases require explicit reconciliation. Supporting multiple WorkItem
vocabularies, including the native tracker's schema:Action, does not select
a graph or make an unqualified catalogue query a current-tracker query.
Review both the graph scope and the vocabulary of each consumer.
For a bounded diagnostic, supply a small explicit VALUES ?item set of
known identifiers and inspect their asserted types separately in default and
the configured records graph. Use ?item a ?type with an exact type
FILTER and a row limit large enough for the supplied identifiers and types.
Include a known positive control in each scope, check truncation, and report
the identifiers, graph, query and result. A failed query is UNKNOWN. A positive
control proves execution, not population coverage. Read labels separately
rather than adding a broad join.
For current state, use a consumer that explicitly selects the records graph and its latest-state model, such as the planned-work queries. Those queries have their own supported-input and result-limit boundaries. Catalogue source presence is separate from installed query definitions and observed live acceptance.
This signpost authorizes no union into default, graph promotion, alias rewriting, retraction, backfill, or query-library replacement. A retrospective repair needs producer provenance, consumer coverage and a separate reviewed decision. Preserve the historical facts while investigating that decision.
Observed WorkItem consumer authority
The cost publisher reads deterministic tracker WorkItems from crew:records.
This is an explicitly granted read of observed evidence, not a move into ROOT
and not promotion of inferred facts. publish_work_cost.py requires the
read:crew:records capability under its own consumer name in
config/plane-authority.json before any graph access, including unchanged
snapshot runs. A missing, malformed, or unrelated grant refuses publication
and leaves the successful cursor intact. The actor argument cannot confer it.
The administrator authorized this grant on 2026-09-16 (aegis-trgavj). The
human-maintained file remains the authority; CAMAYOC_AUTHORITY selects an
operator-provisioned alternative, as for plane promotion. This is an
application policy gate, not an operating-system sandbox against a principal
that can modify the executable or its environment. Read capabilities never
satisfy the separate plane-promotion checks. Source facts remain in their
observed graph; the publisher still checks direct WorkItem typing before it
writes usage and verifies attribution in the same graph afterward.
Standing tracker delivery
scripts/sync_work_items.py polls the public br list --json interface with
an explicit --db. Its scope is claimed work plus assigned open/blocked work,
including deferred assignments. It accepts the legacy array or a complete list
envelope; a truncated response is UNKNOWN. This is independent of cost records:
only the authoritative tracker can supply canonical WorkItem identity.
Each tick handles one record, at most one episode write and two verification reads, with a persisted 60-second minimum interval and a 256-KiB episode cap.
A reviewed capacity trial can opt into at most four distinct records per eligible
tick without changing that default. The scheduler reads an optional policy beside
its state file: state.json uses state.batch.json. A policy carries integer
max_items (1–4), numeric epoch starts_at and expires_at, and a nonempty
review reference. Its window must be positive and no longer than one hour.
Missing, future or expired policies use one record; malformed policies report
UNKNOWN without making graph requests. The review field is an audit pointer,
not an authorization check: obtain operational review before installing a policy.
Installation of this code alone enables no trial.
The request ceiling is three per record and twelve per batch. A record occurs
only once in a batch, so controlled absent reads cannot become a same-tick retry.
The strict sixty-second gate, serialization, immutable pending bodies and fair
queue remain in force. Receipts carry aggregate counts, processed items, the
effective max_items, and the policy's expiry/review when present. BACKOFF is the
persisted time gate before graph requests; it does not itself measure writer
contention. Expiry or policy removal restores one record at the next admission,
without restart. An already-admitted bounded batch may finish.
Before activation, record a matched baseline and agree on latency, error and scheduler-backoff stop thresholds. Use an isolated, explicitly coordinated window; the earlier batch trial improved delivery but crossed its fleet latency guardrail under mixed workloads. Do not retain a larger budget on throughput alone. Expiry is a final bound, not a substitute for stopping a degraded trial.
Current claims win initial ties, then priority and newer creation time. Queue ordering uses the later of first-seen time and last-attempt time. A new arrival therefore cannot indefinitely jump ahead of an existing unverified transition, and an attempted record rotates behind work already waiting. Initial backlog drains gradually; consumers continue refusing absent identities until delivery. This does not enlarge any consumer's request budget.
The existing mapper owns all semantics: stable WorkItem identity and an immutable
content-addressed Observation in crew:records, never ROOT. --source names the
tracker authority and the item ID is appended to it. Exact payloads are persisted
before writes. Delivery requires a positive same-plane WorkItem control and a
read-back of the exact direct WorkItem/Observation assertions. Neither unchanged
nor a nonzero transaction count proves delivery. Lost responses keep the payload;
two separately scheduled controlled absent reads permit a byte-identical retry.
After three fast attempts, recovery permits at most one further attempt per hour,
only within 24 hours of the retained first-write time. This prevents one short
store incident from permanently consuming the entire recovery budget.
Changed tracker fields cannot replace an unresolved payload.
Confirmed versions receive a periodic 24-hour read-back. Retraction is detected
and enters the same pending reconciliation path. Receipt and status metrics report
errors and backlog age; a backlog older than twice its bounded drain interval
(minimum fifteen minutes) is unhealthy. --publish-status emits independent
camayoc_workitem_ingress_* metrics, including timestamp and exit status, on every
run. Operators must arm status, stale and absent detection with the scheduler.
The scheduler should run this prerequisite before consumers; consumers retain
their own canonical-presence checks.
Standing WorkItem recovery receipts
These JSON adapter rules apply to repository-local tracker stores. The primary Seeds lane uses the separate SQLite custody implementation; this change neither imports legacy pending state nor rearms that lane.
The standing tracker sync distinguishes DEGRADED from UNKNOWN:
verified delivery with retryable indeterminate entries is degraded (exit 0),
while a tick unable to verify pending work, invalid records, exhausted attempts,
or an over-age backlog remains unknown (exit 2). Healthy no-op ticks remain OK.
Receipts and metrics expose the indeterminate count independently of backlog.
Recovery receives priority on alternating ticks; the other ticks retain
least-recently-attempted fairness. This prevents both a fresh backlog delaying
all recovery reads and a broken control starving fresh work. Retry writes still
require two separately scheduled controlled absent reads and the original body.
Failed or truncated reads discard accumulated absence evidence. Every write
persists the next hourly retry time before sending; restart cannot shorten it.
An old exhausted entry without that clock starts a fresh one-hour wait. Missing,
invalid or future first-write clocks never authorize extended retries, and the
first-write time is never reset. At 24 hours, further writes stop but controlled
reads may still confirm a previously landed body. Repeated outage remains UNKNOWN.
Definitive HTTP 4xx refusals are retained across subsequent reads and prohibit another write (408 and 429 remain transient). Old retained 4xx status without an endpoint is held conservatively rather than silently forgotten. Neither a healthy control nor an absent target erases that refusal. No response bodies are logged.
pending_oldest_seconds measures time since a retained body entered recovery,
not time waiting in the delivery backlog. Its started_at is persisted before
the first write and survives reads, retries, tracker changes and restarts.
Backoff ticks recompute the age without making requests. Missing legacy clocks,
invalid clocks or a clock in the future report -1 (unknown), never zero.
exhausted_retries counts pending bodies whose three fast attempts are exhausted,
including those waiting for bounded hourly recovery;
invalid attempt counters also report -1. Both are exported with the
camayoc_workitem_ingress_ prefix. Confirming every body clears both to zero.
These signals distinguish designed recovery from stuck delivery. The default
one-item/three-request/one-write per-tick budget is unchanged; lifetime attempts
extend only within the recovery window. No live replay or backfill is authorized.
Quipu client credentials
Plane registration and governed RML writes share the credential order
QUIPU_AUTH_TOKEN (trimmed), QUIPU_AUTH_TOKEN_FILE, then
~/.config/quipu/token. An explicit in-process credential remains the caller's
override. An explicit missing file does not fall back to another credential;
the former legacy location must be selected explicitly.
Missing, invalid, unreadable or rejected credentials produce one diagnostic and
disable later writes for that server/session. A private marker under the Camayoc
state directory carries this state across hook processes when a harness session
ID is available. The diagnostic names process-only suppression when no session
ID exists or persistence fails. Public reads remain available. Obtain an accepted
credential from the service administrator, verify it with caboodle doctor, and
start a new session after repairing a refused credential.
Replacing one promoted fact
Use promote_plane.py --triples-file replacement.nt --supersedes old.nt --to crew:declared --by <authorized-reviewer> --authored-by <different-author> --reason <evidence> to supersede one exact target fact. Both files contain one
N-Triples statement, with the same subject and predicate and a changed object.
The replacement must already exist in crew:inferred; the old fact must exist
in the target, with no competing value for that property. Existing authority
and no-self-promotion rules apply. --emit performs the preflight reads and
prints the proposed conditional update without writing.
The server rechecks those conditions under its writer lock. One target-graph
transaction closes the old fact, asserts the replacement, and records both exact
statements as camayoc:supersededTriple and camayoc:replacementTriple on the
plane-promotion record. The record digest includes both statements and target.
Other facts in the carrier episode remain untouched; the old fact remains
available through historical reads. Store commit time determines the interval
close; the optional timestamp only dates the audit record.
This mode leaves the inferred source open and refuses --source-episode.
It verifies new target presence, old target absence, and source presence after
the write. A refused or indeterminate write is never automatically retried.
An existing client-side authority grant is still required; this does not enroll
principals or grant authority merely because someone supplies a --by value.
Production replacement remains a separate governed operation.
The supersede client requires a server that reports /update transaction counts
and graph identity; the integration suite pins the verified Quipu 0.14.1 release.
A legacy server's empty response is UNKNOWN, even if its write committed. Verify
the resulting state before any retry; an empty response is never called success.
Design: The Bootstrap Ontology — the upper vocabulary every domain imports
Implementation status (2026-08-22): ✅ Built, the way this document asked to be built — slice by slice, question first.
ontology/core.ttl+shapes/core.shapes.ttlnow carry the candidate set and three slices beyond it:WorkItemandDecisionlanded as classes,Outcomelanded as theaegis:outcomeproperty (what happened is a fact of the work item, not a node), andsourceKindis minted and SHACL-mandatory exactly as §2 specified. One deliberate departure from §1's letter, recorded inontology/core.ttl's namespace note: terms are declared in the store's configured base namespace (aegis:by default) so that quipu/episodewrites and the declarations agree out of the box — the namespace is still a parameter, set in that file's prefix, never a hardcoded hostname. Every term remains owed to a question in competency/, now six suites (workflow-and-archive joined on 2026-08-24;just competency-listis the living count and watermark); per-question coverage is measured byjust query-coveragerather than asserted here.
1. Principle: reuse before minting
The stack already has vocabulary with settled semantics. The bootstrap ontology's first job is to not duplicate it:
| Existing | Namespace | Reused for |
|---|---|---|
aegis: (quipu shapes) | governance atoms: Policy, Principal, authorityOver, reports_to, appliesTo, verdicts | agents, authority, governance |
prov: (W3C PROV-O) | wasGeneratedBy, activities | episode provenance (quipu supplies this automatically on episode writes) |
quipu: control vocabulary | labels (freshness, trust, policyClass), datasets, packs | trust posture, planes |
bobbin: code entities | CodeModule, CodeSymbol, Document, Section | task ↔ code linkage (later slice) |
aegis:appliesTo is listed there as a standing reuse constraint rather than
a current dependency — camayoc uses it nowhere today. quipu declares it for
repo-relative path globs scoping where an action-boundary policy binds. The
2026-08-25 evaluation (camayoc-95a) read the whole suite against it and
found no question that needs path scoping: two questions carry an <area>
parameter (crew-task-lifecycle 6, verification-and-liveness 2) and neither
defines <area> as a path, so reading one as a path glob would be inventing
the meaning rather than reusing the term. The row exists so that if a camayoc
rule ever does scope by path, it reuses this term instead of minting the
equivalent — which is the failure the table is here to prevent.
Camayoc mints terms only where no owner exists. Those live under a
parameterized namespace — camayoc: resolving to a deployment-configured
IRI base, never a hardcoded hostname (the lesson shantytown's quipu client
already records: a namespace is a parameter, and a literal hostname in a
public repo is two faults with one cause).
2. The upper terms (candidate set — the slice decides)
Small and brutally curated. Current candidates, each owed to a competency question in competency/:
camayoc:WorkItem— a unit of intended work (a shantytown task, a bead, an issue). Deliberately tracker-agnostic: shantytown's adapter stance is "bring your own tracker," so the ontology models the work, not the tracker's message format. (Modeling the message instead of the domain is the classic failure; the tracker record is evidence, never the model.)camayoc:Decision— a choice made in the course of work: what was decided, the alternatives visible at the time, who/what decided, and under which work item. The load-bearing class of the first slice.camayoc:Outcome— what actually happened: done, abandoned, superseded, failed-with-diagnosis. Distinct from the decision that aimed at it.camayoc:sourceKind— the ingress tier tag, mandatory on every camayoc-ingested fact:observed(deterministic parse of a record that exists),declared(a human said so),inferred(a model concluded it). See ingress.md — this is the generalization of NeuralAmplifier'sruleTier, and SHACL refuses facts without it.- Roles, not subclasses. A work item plays roles (clinical/financial in the claims framing; here: a task is simultaneously a unit of dispatch, a provenance anchor, and a decision context). Facets over deep hierarchies — the semilattice stance, applied to the ontology itself.
Agents are not re-modeled: an agent is an aegis:Principal with
shantytown's reports_to shape, exactly as the graph already holds them.
3. Vocabulary discipline (all inherited, all mandatory)
rdfs:rangeonly — neverrdfs:domainon shared predicates. A domain declaration on a predicate used across classes silently retypes whatever it touches once a reasoner materializes it (quipu's recorded Q-SARC-VOCAB lesson).- SHACL posture: permissive on domain shape, strict on provenance. Closed
sh:invocabularies withminCount 1onsourceKindand its companions from the first load — the tag is the reader's only signal of trust. Domain properties start permissive and tighten deliberately. - Facts true at write time; judgments at read time. Shantytown's event store learned this the hard way: a liveness verdict stamped at emit is stale by the time it is read. The ontology never contains a class or property whose value is a judgment that decays — currency, liveness, and status-now are queries, not stored fields.
- Contested is first-class. Two sources disagreeing produce a contested pair, queryable as such; resolution is an explicit, provenance-carrying act.
4. Packaging
The bootstrap vocabulary ships as core.pendant.db (quipu #81): the upper
terms, their SHACL, and the generic competency queries (e.g. "what decisions
touch entity X"). Domain packs (crew.pendant.db first) declare it in their
manifest's default dataset. Until packs land in quipu, the same content lives
here as Turtle + shape files loadable via POST /shapes — the pack is the
distribution format, not a prerequisite.
5. Related
- ingress.md — how facts carrying this vocabulary enter quipu.
- task-lifecycle-slice.md — the slice that mints the first real terms.
- quipu
docs/design/graph-labels.md— the trust/freshness lattice this vocabulary's planes are labelled with. - quipu
docs/design/knowledge-packs.md— the artifact format. - NeuralAmplifier
docs/knowledge-architecture.md— the proven instance of the tier-tagged, SHACL-refused ingress posture this generalizes.
Design: what belongs in the graph
Status (2026-08-07): guidance, derived from measured incidents on a live crew. No classes proposed here — this is the decision procedure that should precede proposing any.
Four properties decide whether a fact belongs in this ontology: decay, reproducibility, truthiness, importance. They are not a scoring rubric. They are an ordered procedure, and two of them interact in a way that is easy to miss.
1. Does it DECAY? — if yes, never store it
A fact that stops being true is a liability, not knowledge. ingress.md rule 5
already forbids the shape: "no stored judgment that decays (liveness, currency,
'still in progress'). Judgments are queries at read time."
Canonical decaying judgments: current latency, is-this-agent-alive, is-this-work- in-progress, remaining quota. Store none of them. Store what makes them answerable — a retrieval method, an event stream, a definition.
Measured cost of getting this wrong in the other direction: alert keeper labels are static while liveness is not, so seven of ten active alerts were owned by principals that had stopped, and nothing reported it.
2. Is it REPRODUCIBLE? — if yes, store the method, not the value
Can the fact be re-derived from a durable source?
- Reproducible — metric definitions (re-parse the rules), code structure (re-walk the repo), token counts (re-read the session files). Storing these is a cache: losing it costs re-derivation, not knowledge.
- Not reproducible — why a decision was made, what alternatives were visible, what an agent was holding when it stopped, a lesson learned at 3am. The graph is the only copy. Losing it is permanent.
The graph's real job is the second category. The first belongs in it only as a pointer — which is the whole design of the metrics slice: the ontology says the metric exists, what it means, who owns it, and how to fetch it; the samples live in the time-series system.
3. ⚠ REPRODUCIBILITY HAS A HALF-LIFE — and this is the trap
"Reproducible" is a claim about a source that still exists. Check the source's retention before deciding to store only the method.
Measured on a live host, 2026-08-07, while designing exactly this:
claude transcripts 709 files, oldest ~27 days old
codex sessions 7 files, all from the day codex was installed
retention policy NONE CONFIGURED, in either tool or in cron
So token-cost facts are reproducible today and unverified for next month. Two readings, both bad and both unmeasured: if something prunes at 30 days, the oldest are about to vanish; if nothing ever prunes, this is unbounded disk growth nobody is watching.
Consequence, and it revises the metrics decision rather than contradicting it. Metric samples are reproducible from a TSDB with a stated retention — store the method. Token costs are reproducible from files with unknown retention, and a cost-per-work-item is small, final, and joins to entities already modelled — store the aggregate, not just the reader. The rule is not "never store reproducible facts"; it is store the method when the source will outlive the question, and the value when it will not.
4. TRUTHINESS matters most where reproducibility is lowest
sourceKind does its heaviest work in exactly the place the graph is most needed.
A reproducible fact can be re-checked, so a wrong tag is recoverable. A
non-reproducible fact can only be trusted — nothing can ever re-derive it.
Which produces the uncomfortable quadrant: high value, not reproducible,
inferred. Session lessons, failure diagnoses, "we tried X and it did not
work." The most expensive knowledge this stack handles is also the least
trustworthy by tag and the most fragile.
That is an argument for capturing it aggressively into the low-trust plane —
not for relaxing the tag. Quarantine plus a promotion path is what lets valuable
uncertain knowledge exist at all without contaminating what is certain
(ingress.md rule 4).
4b. DURABILITY IS THE ADDRESSABLE FORM OF REPRODUCIBILITY — declare it, do not infer it
Minted upstream (2026-08-24): the lattice this section proposed now ships in quipu (
src/lattice.rs:soleRecord < reproducible < backed, composed by meet), and deep freeze is its first mover: freezing a window graph relocates the rows into a hash-verified archive pack and declaresbacked— durability-declared relocation, exactly this section's framing, and never §5's write-time filter. See quipudocs/design/graph-kinds-and-deep-freeze.md.
Sections 2 and 3 leave "is the graph the only copy?" as something a reader infers from whether a derivation method is present. That is too weak to act on. An inferred property can only be queried by ABSENCE — "show me facts missing a field" — which is a different and worse question than the one that matters:
which facts would be LOST if this store were lost?
So durability is declared explicitly, per fact:
SoleRecord the graph is the only copy. Loss is permanent.
Reproducible re-derivable from a source that still exists.
Backed independently persisted outside this store.
It composes. A derived fact is only as durable as its least durable input — join a sole-record fact with a backed-up one and the result is sole-record, because you cannot reconstruct it without the fragile part. That is a meet, which makes durability a lattice axis alongside trust and freshness, while the derivation method stays a per-fact value (two methods do not meet into a third).
Two constraints inherited from the freshness axis, both mandatory:
- Declared, never synthesized. Nothing may infer
Backedbecause a backup ran once. The existing freshness type states the rule for itself — "a producer declares it; there is no synthesized fresh tag, ever" — and it holds here. - Undeclared is undeclared. An unknown or missing durability must never
default to
Backed. Not-measured rendering as safe is the exact failure this whole document exists to prevent.
And durability decays, for the reason in §3. Reproducible is a claim about a
source that still exists; when that source rotates, the fact silently becomes
SoleRecord and nothing notices. The tag records what a producer asserted at
write time, so something must periodically re-assert or downgrade it — the same
cadence problem freshness has, which is the argument for putting both on the same
machinery rather than in prose.
5. IMPORTANCE gates CURATION, not storage
Importance should decide what earns an agent's attention, never whether a fact is allowed in. Storage is cheap; curation is not.
And importance is usually only knowable later. "st reports success for a message that never arrived" read as a papercut until it silently stranded a production go-ahead. A filter applied at write time on predicted importance will discard the thing that mattered.
The procedure, condensed
decays? -> do not store. store what makes it answerable.
reproducible? -> store the method — IF the source outlives the question.
check retention. it is a claim, not a property.
not reproducible? -> the graph is the only copy. store it, and the
sourceKind tag is carrying the entire weight.
importance -> sets curation effort. never a write-time filter.
Design: The Skill Is the Interface
Implementation status (2026-10-17): 🟡 The skill and server-side stored-query loading path ship.
scripts/bootstrap.shloads the named definitions inqueries/through Quipu's/queriesAPI; the server lists and runs them through/ask. Fixture tests exercise the definitions, andjust query-coveragereports coverage and names gaps without freezing a count in prose. Getting started documents loading. Labels also ship:/graph/create+/graph/labelare used byscripts/planes.pyfor quarantine planes. These implemented APIs do not prove that every deployment has loaded the definitions or that every competency question has faithful answers from real records. Raw SPARQL is an alternative for inspection, not a workaround for an unbuilt #79 API.
1. The usage model
Camayoc's primary consumer is an agent in a session, not a pipeline. The unit of distribution is therefore a skill: a package of instructions an agent loads that teaches it how to use governed memory — when to query, when to record, how to tag, and how to stand a store up from nothing. Harness adapters and record parsers are optional enrichment; the skill is the first-class surface.
This kills two design risks at once:
- No hard dependency on any harness. Shantytown, Claude Code, a bare
script — any agent that can speak HTTP to a quipu can follow the skill.
Shantytown's reserved
knowledgeadapter remains a welcome integration (and its none-adapter proves the independence in the other direction), but nothing in camayoc requires it. - Guidance and enforcement stay separate. The skill guides the agent (record decisions at the moment they happen, tag honestly, query before deciding). The SHACL shapes enforce (an untagged write is refused by the store, whatever the agent believed). A well-behaved agent rarely sees a refusal; a misbehaving one cannot do damage the shapes prohibit. The skill is UX; the shapes are the contract.
2. The four pillars, as the skill enables them
| Pillar | What the skill has the agent do |
|---|---|
| Bootstrap | Stand up governed memory from nothing: check the quipu is reachable, load ontology/ + shapes/ (idempotent), verify with a probe write that SHACL refusal is live — prove the gate is on, not assume it. |
| Query first | Before deciding or re-deriving: run the named competency queries loaded by bootstrap through /ask; the skill also carries raw-SPARQL patterns. "What did we decide about X and why" is asked before X is re-decided. |
| Record at the moment | When a decision happens, write it then — an episode carrying a Decision (what was chosen, over what, why, under which work item), tagged with its honest sourceKind. Work items and outcomes likewise. Session end = outcomes recorded, not summarized into oblivion. |
| Tag honestly | observed = a record exists and was parsed; declared = a human said so (quote them); inferred = the agent concluded it — and inferred lands in the inferred plane, low-trust, promotable later. Never up-tag. The store refuses untagged writes; the skill makes the honest tag the path of least resistance. |
3. What the skill is not
- Not a wrapper library. It contains instructions and canonical request
shapes (quipu's
/episode,/query,/shapes— branch onoutcome, never oncount), not code to vendor. An agent with Bash and HTTP has everything it needs. - Not a harness hook. It does not assume stop-hooks, inboxes, or any lifecycle. A harness that has those (shantytown) can invoke the same moves automatically; that is that harness's integration, not the skill's concern.
- Not a substitute for the shapes. Nothing in the skill is load-bearing for integrity. Delete the skill and the store is exactly as safe — just harder to use well.
4. Consequences for the rest of the repo
- Record parsers demote to enrichment. The task-lifecycle slice's spine
is the skill-guided agent recording its own work; deterministic parsers
(git history, a harness's task records) backfill and corroborate — they
are
observed-tier enrichment, valuable and optional. - The competency suite doubles as the skill's query cheat-sheet. One file, two consumers: the eval gate runs the questions; the skill quotes them.
- Packs carry the skill. When quipu #81 lands, a domain pack's manifest references the skill version it was authored against — attach a pack, get told how to use it.
5. Related
skills/camayoc/SKILL.md— the artifact.- ingress.md — the discipline the skill teaches; the shapes that enforce it.
- task-lifecycle-slice.md — the first slice, re-centered on the skill.
- competency/crew-task-lifecycle.md — the questions the skill tells an agent to ask.
Design: First Slice — task lifecycle + decisions
Implementation status (2026-10-17): 🟡 The spine is built and proven end-to-end:
ontology/core.ttl+shapes/core.shapes.ttlship (v1, deliverable 1), the skill and plugin ship, and the loop was verified against a live quipu withvalidate_on_write— bare store → bootstrap → untagged probe refused → tagged Decision accepted → competency-style query answers → session hook reports ACTIVE. Since the 2026-08-06 banner, deliverable 5's substance landed injust testrather thanjust check: the stored queries execute against seeded fixture graphs with positive and control-negative arms (tests/test_competency_queries.py,tests/test_golden_path_queries.py), andjust query-coveragereports per-question coverage with gaps named. Deliverable 4's stored-query loading and serving path ships:scripts/bootstrap.shPOSTs definitions fromqueries/to/queries, and Quipu lists and runs them through/ask. Counts and gaps come fromjust query-coverage; loading still has to succeed on each deployment. Real-record answer acceptance below remains separate from API availability. Remaining: the crew-domain vocabulary beyond core (deliverable 2 — noontology/crew.ttlexists; later slices minted into core instead) and the optional shantytown-records enrichment parser (deliverable 6 — unbuilt; the deterministic parser that does exist,scripts/ingest_git_provenance.py, covers git provenance, a different record source). No term is minted until a mapping row or competency question needs it.
1. Why this slice
"What did we decide about X, and why" is the highest-value question a crew's memory can answer, and the task lifecycle is the provenance spine every decision hangs from. It is also the slice with the most honest data source: shantytown's task/crew records exist today, are deterministic to parse, and their event architecture has already learned exactly which facts an event can carry truthfully.
2. Two paths into the graph — skill first, parser as enrichment
The spine is the skill-guided agent (skill.md): decisions, work items and outcomes recorded as tagged episodes at the moment they happen, by any agent in any harness. That path needs nothing built beyond the ontology, the shapes, and the skill this repo already ships.
The enrichment path is a deterministic parser over a harness's records —
shantytown's are the worked example below — backfilling and corroborating
the skill-recorded spine with observed-tier facts. It is optional, and no
harness is a dependency.
The record mapping (enrichment parser, st as the worked example)
One episode per record batch, all facts tagged sourceKind: observed, into
the crew:records plane. Candidate mapping (the implementation refines
against real records; the shape is the commitment):
| St record | Episode facts |
|---|---|
task created (st task "…" → st-1) | crew:st-1 a camayoc:WorkItem ; rdfs:label "…" ; camayoc:createdAt <ts> |
| task assigned / inboxed to agent | crew:st-1 camayoc:assignedTo <agent-iri> (the agent is the existing aegis:Principal) |
| agent stop with item held | camayoc:workedOn occurrence with ts, item_status — exactly the payload st's event store records, no more (no liveness verdicts: facts true at write time) |
| task done / closed | crew:st-1 camayoc:outcome camayoc:done ; camayoc:closedAt <ts> |
| decision recorded | camayoc:Decision node: decidedIn crew:st-1 ; decidedBy <principal> ; chose "…" ; over "…" ; rationale "…" |
Decisions are the one place the slice needs a record st does not currently
write: a decision record. Two paths, both kept open: (a) a lightweight st
convention (a decision: line in task notes the parser lifts), and (b) the
session adapter writing decisions into crew:inferred until a human or
convention promotes them. Start with (a) — declared/observed beats inferred.
3. Competency questions = acceptance tests
The slice's question set lives in
competency/crew-task-lifecycle.md
and doubles as the test suite: the slice is done when each question runs
as a named stored query through the implemented /ask API against a graph
built from real
shantytown records, and an agent (or Stiwi) accepts the answers as faithful.
That definition-of-done is stolen deliberately from the claims planning doc:
model with sign-off, real historical data validated, questions running as
named queries, and a trace a reviewer accepts.
4. Deliverables
ontology/core.ttl+shapes/core.shapes.ttl— the bootstrap terms the slice actually needs (and no more).ontology/crew.ttl+shapes/crew.shapes.ttl— the crew domain.- The skill, exercised end-to-end:
skills/camayoc/SKILL.mdships already; the slice proves it — a skill-guided agent bootstraps a bare store, records a real session's decisions/work/outcomes, and the competency questions answer from what it recorded. - The competency queries as stored queries: bootstrap loads
queries/through/queries, and/askserves them. Raw-SPARQL patterns remain available in the skill. Pack delivery and acceptance on real records are separate from this implemented loading path. - An eval gate:
just testruns the questions against a fixture graph — built from skill-recorded episodes, optionally corroborated by the enrichment parser. - (Optional, unblocking nothing)
camayoc crew ingest <records-root>— the deterministic record→episode enrichment parser, st as the first worked example.
5. Scope boundaries (honest)
- No task↔code linkage in this slice (
aegis:modifiesjoins come with the git adapter, next slice) — the decision spine must stand alone first. - No failure-memory ("has this been seen before") — needs the inferred plane and its promotion story; deliberately after the observed spine.
- No harness dependency, period. The spine is skill + HTTP. The
enrichment parser reads records from outside; any harness-native emitter
(st's reserved
knowledgeseam) is that harness's integration, on its schedule. - Nothing here claims real-time. Ingest is batch/poll; the crew's live coordination stays in st where it belongs.
Design: Workflow runs and the archive — the second routing dimension
Implementation status (2026-08-25): ✅ Built, question-first, and now answering. competency/workflow-and-archive.md landed before any term; the workflow slice (
aegis:WorkflowDefinition/WorkflowStep/WorkflowRun/TransitionEventand their properties) is minted inontology/core.ttlwith each term owed to a question. Routing is 2-D:plane_for(source_kind, data_kind="knowledge")inscripts/planes.py(the default preserves every pre-2-D caller because every static plane IS a knowledge plane — a pinned fact, not a fallback), andoperationaldata is window-addressed viascripts/windows.py({WINDOW_NS}{family}/{YYYY-MM}, create-and-label-or-neither, the scheme pinned bytests/test_planes_2d.pyagainst shuttle's reimplementation). The camayoc-913 move rule is decided and implemented inscripts/promote_plane.py: assert in the target, CLOSE the source episode (POST /episode/retract, a bitemporal close,on_orphan: preserve), record the move; without--source-episodethe record carriescamayoc:sourceLeftOpen trueout loud. Existing plane IRIs are untouched (bobbin compiles them in). Deliberately not built here: the freeze mechanics (quipu's, see itsgraph-kinds-and-deep-freeze.md) and shuttle itself (scbrown/shuttle).The questions run (2026-08-25, camayoc-rkb). Eight of the fourteen are named stored queries —
queries/camayoc_wf_*.json, Q1–Q5, Q7, Q8 and Q13 — executing againsttests/fixtures/workflow-archive.trig, a TriG dataset holding a hot window, a frozen window, the definitions plane and the identity graph. 36 tests intests/test_workflow_queries.py; Q13 replays against the episodescripts/promote_plane.pyactually emits rather than against fixture triples written to match it. Coverage: 8/14, six gaps, nothing unwritten. Q9–Q12 and Q14 are the store-surface boundary — graph kinds, freeze state and thaw records are meta-graph and store-table properties served byGET /graphs, not triples a query can reach — and Q6 is the crypto boundary: SPARQL fetches a public key and cannot evaluate a signature against it. No term was minted to close a gap.
Status: shuttle exports workflow runs — high-volume, high-churn operational records — into quipu. This is the ingress discipline for that class of data: what it means, where it lands, and how it ages out.
1. The second dimension
Ingress routing was 1-D: sourceKind → plane. The planes answer how much
to trust a fact; they say nothing about what sort of data it is. Workflow
runs are observed facts, but routing them into crew:records would grow
the knowledge plane without bound and make nothing freezable — the graph is
quipu's unit of pack/attach/label/authority, and freezing crew:records
would archive the crew's task lifecycle along with the runs.
So the router takes (source_kind, data_kind):
knowledge(default): the static planes, exactly as before;operational: refused byplane_for, pointing at the window scheme — one graph per producer-family per month, labelledoperational/fresh/soleRecordat creation, frozen whole when its runs are terminal (the freeze relabels toarchive/backed);- anything else: refused, never ROOT.
2. The move rule (camayoc-913, closed)
Promotion and freeze are both graph moves, and both now follow one rule: assert in the target, close in the source, record the move. The close is bitemporal — quipu closes the valid interval, never deletes — so the original stays visible as-of its write time (competency 13). The worry that kept 913 open was that closing erases the fact's low-trust past; bitemporality answers it.
The promotion close operates on the source episode, because the episode
is the unit ingress writes and POST /episode/retract is the graph-honest
retraction surface (triple-level /retract is ROOT-scoped). Commit order is
assert-then-close: a failed close leaves the fact readable in two planes at
different trust — visible, recoverable, and reported loudly — while
close-then-failed-assert would lose the promoted fact.
3. What shuttle writes (the export contract)
Append-only aegis:TransitionEvents (step, from-state, to-state, time,
aegis:performedBy + aegis:signature — the hex ed25519 signature over the
canonical shuttle-transition-v1 message), with aegis:currentState
re-asserted per transition as derived convenience. Keys live as
aegis:VerifierRegistration facts in a dataKind=identity graph that is
never frozen, so signatures in a frozen window stay verifiable. The
signature terms are quipu-owned aegis vocabulary, reused not re-minted.
4. Related
- quipu
docs/design/graph-kinds-and-deep-freeze.md— the store mechanics. - ingress.md §2 — the planes this extends.
- what-belongs-in-the-graph.md §4b — the durability lattice freeze declares against; §5 — why freeze is post-hoc relocation, never write-time filtering.
scbrown/shuttle— the producer.
Design: Golden Paths — verified trajectories, blessed and enforced
Implementation status (2026-08-22, camayoc-gp1 + gp2): 🟡 Camayoc's slice is done to its deferral line: terms minted, gates armed, queries stored at 12/16. The competency suite (competency/golden-paths.md) is written; the slice decided (see the §3 note) and the vocabulary is minted in
ontology/core.ttlwith shapes inshapes/core.shapes.ttland three refusal arms inscripts/gate_probe.sh(proven able to fail bytests/test_gate_probe.py). Q1–Q13 minus Q5 run as named stored queries (queries/camayoc_gp_*.json) against a seeded fixture (tests/fixtures/golden-paths.ttl, positive + control-negative arms intests/test_golden_path_queries.py; camayoc-gp2). The four gaps are blocked outside this repo, still, as of this date: Q5 waits on quipu'spath conecommand (quipu-gp2, design-only — the term for per-step cone membership is deliberately unminted until that command's output shape decides it); Q14–Q15 are deferred with the L5 ladder levels per the suite's acceptance note —aegis:derivedConstraintis minted but the Policy/grant/act records to join against only exist once quipu's verdict-signing-gated constraint derivation does; Q16 waits on guard-written conformance records at scale (yupana FR-41, design-only —aegis:deviatesAtis minted and ready). Mechanisms remain design-only in quipu and yupana. Mechanism designs live with their owners: quipu docs/design/golden-paths-blessing.md (storage, pruning aid, backtest, promotion) and yupana docs/golden-path-guard.md (conformance guard, plan pre-check).
1. The arc
An agent picks up a work item and uses the graph to understand it. When a work item completes with verified results, its trajectory — the ordered steps that actually got it done — becomes raw material. Trajectories that earn it are pruned (steps that were correct but didn't serve the goal are cut, with humans ruling on anything the deterministic analysis can't) and promoted into golden paths: blessed trajectories that guide similar future work. At the far end of the ladder, a blessed path backs authz constraints — an agent is permitted to act because its plan instantiates a path that demonstrably worked.
Blessing is earned two ways, and only two:
- Deterministic verification. Falsifier-gated Verifications on the result, provenance-cone analysis on the steps, backtests over recorded history. Small, checkable, mechanical — the kernel that untrusted work must satisfy. (This is the honest extent of the Lean 4 parallel: a tiny trusted checker, untrusted generators producing artifacts it can refuse. Nothing here is theorem proving; everything here is refusable evidence.)
- Human promotion. Every level transition is a human act, recorded as a fact with provenance. The machinery narrows what a human must look at; it never replaces the looking.
2. Reuse before minting
The slice reuses settled vocabulary first; camayoc mints only what no owner has.
| Existing term | Owner | Reused for |
|---|---|---|
aegis:WorkItem, aegis:outcome, aegis:closedAt | camayoc core (slice 1) | the unit of work and its close disposition |
aegis:Decision (chose, over, rationale, decidedBy) | camayoc core (slice 1) | pruning rulings and promotion rulings |
aegis:Verification + aegis:falsifier | camayoc core (slice 2) | the "verified results" admissibility gate |
aegis:supersededBy | camayoc core (slice 1) | path demotion/replacement, never deletion |
aegis:sourceKind | camayoc core (ingress) | observed trajectory vs declared intent vs inferred similarity |
aegis:exemplar | quipu governance (shapes/governance.ttl) | a path citing the concrete trajectories it was distilled from |
aegis:Policy, aegis:effect, aegis:OperatingPoint | quipu governance | the constraint a blessed path eventually backs |
prov: activity/derivation | W3C PROV-O (quipu episodes) | step ordering and the provenance cone |
3. Candidate terms (the slice decides)
Each candidate is owed to a competency question (cited). All are minted in
the store's base namespace per the core.ttl namespace note, rdfs:range
only, never rdfs:domain.
Slice decisions at minting (camayoc-gp1): omissions and promotions are reified —
omitsSteppoints at aPathOmissionnode (omittedStep+ mandatoryomissionAuthorityofcone-analysis|human-decision, optionalomissionRuling→ Decision), and the promotion properties live on aPathPromotionevent node (promotes,blessingLevel,promotedBy,promotedAt,promotionEvidence) — so each ruling carries its authority without waiting for statement-level attachment. And the required provenance of a GoldenPath isprunedFrom(an in-graph exemplar Trajectory);aegis:exemplarstays available, optional, for exemplars living outside the graph, matching quipu's governance use.
| Candidate | Kind | Owed to | Meaning |
|---|---|---|---|
Trajectory | class | Q1, Q2 | The observed, immutable record of the steps a work item actually took. Never edited after the fact; pruning creates a new entity, it never touches this one. |
Step | class | Q1, Q4, Q5 | One action in a trajectory: actor, action kind, target, the Decision it enacted, the Verification it produced (if any), order. |
GoldenPath | class | Q6–Q16 | A pruned, promoted trajectory class. Cites its exemplar Trajectories via aegis:exemplar; carries its blessing history. |
stepOf / stepOrder | property | Q1 | Step membership and total order within a Trajectory. |
enacts / verifiedBy | property | Q1, Q4 | Step → Decision it enacts; Step (or terminal claim) → Verification. verifiedBy ranges over aegis:Verification, so the falsifier discipline applies unchanged. |
prunedFrom | property | Q5, Q6 | GoldenPath → exemplar Trajectory it was distilled from (alongside aegis:exemplar, which may point outside the graph). |
omitsStep | property | Q6, Q7 | GoldenPath → Step it deliberately excludes, each omission carrying its authority: cone-analysis or a human Decision. |
deadEnd | property | Q7 | GoldenPath → an abandoned branch preserved as a hazard: "exemplars tried this; it did not help." Negative knowledge, kept queryable. |
blessingLevel | property | Q8 | The level set at promotion time — a fact of the promotion event, like aegis:outcome at close. Never a stored decaying judgment of current fitness (Q16 is answered at read time). |
promotedBy / promotedAt / promotionEvidence | property | Q8, Q10 | The human act and its cited evidence (backtest run, verification set). |
followsPath | property | Q11, Q12 | A live trajectory's declared claim to be an instance of a GoldenPath. A claim, not a verdict — conformance is evaluated, not asserted. |
deviatesAt | property | Q12 | Where an evaluated trajectory departs from its declared path: the step and the manner. |
derivedConstraint | property | Q14, Q15 | GoldenPath → the aegis:Policy it backs, closing the audit chain constraint ← path ← exemplars. |
4. The blessing ladder
Multiple levels, because "correct" is not one thing. Every transition upward is a recorded human promotion sitting on a deterministic admission gate; every level can be left, downward, by supersession.
| Level | Name | Deterministic gate (machine-refusable) | Human act |
|---|---|---|---|
| L0 | recorded | the trajectory parsed from real records (sourceKind observed) | none — everything is recorded |
| L1 | verified | work item closed done AND terminal claim carries falsifier-gated Verifications | none — admissibility is mechanical (Q3) |
| L2 | candidate | pruning complete (every omission carries cone-analysis or a Decision); backtest run and recorded (Q13) | the pruning rulings on in-cone steps |
| L3 | advisory | projected and served warn-tier; conformance recorded, nothing blocked | promotion to advisory, citing the backtest |
| L4 | blessed | advisory period elapsed with conformance evidence accumulated | promotion to blessed; deviation now warns loudly, deny opt-in |
| L5 | constraint-backing | verdict signing exists (see the yupana addendum's Phase-4 caveat — honestly far-horizon) | promotion of the derived aegis:Policy through the existing advisory→enforcing gates |
The ladder deliberately maps onto quipu's existing effect ladder
(warn → enforcing) and its promotion gates — golden paths do not get a
second, parallel promotion machinery.
5. Pruning semantics
A successful trajectory usually contains steps that were correct but did not serve the goal — exploration, a branch that was walked back, scaffolding. Pruning is how a trajectory becomes a path, and it has one rule: the raw Trajectory is immutable. Pruning authors a new GoldenPath that cites what it keeps and records what it omits.
The deterministic aid: compute the provenance cone of the verified result
— the transitive derivation closure from the terminal Verifications back
through the steps' outputs. A step outside the cone contributed nothing the
verified result depends on: mechanically prunable, no human needed
(Q5). A step inside the cone is load-bearing: pruning it requires a human
aegis:Decision with rationale (Q6), because the cone says it mattered and a
human is overruling the cone.
Pruned branches that represent genuine exploration are preserved as
deadEnd hazards (Q7): a follower of the path should know what the
exemplars tried that didn't help. Pruning is curation, never erasure —
bitemporality keeps everything anyway; this just keeps it meaningful.
6. Mechanism assignment
Camayoc owns what these facts mean and how they earn their way in. The machinery lives with its owners:
| Mechanism | Owner | Reuses |
|---|---|---|
| Trajectory ingestion (episodes, PROV) | quipu | /episode path, auto-provenance |
| Provenance-cone computation | quipu | Store::speculate / impact machinery |
| Path backtest over history | quipu | src/governance/backtest.rs pattern, generalized from single-edit exemplars to trajectories |
| Candidate drafting, born advisory | quipu | quipu policy draft scaffold; policy-by-example step 2, generalized |
| Promotion / demotion gates | quipu | existing advisory→enforcing gates, aegis:supersededBy |
| Path projection to the guard | yupana | quipu→yupana policy-projection path, projection freshness served |
| Conformance guard (warn / opt-in deny) | yupana | FR-30 pre-edit hook + FR-35 game-state guard, generalized to steps |
| Plan pre-check (what-if over a step sequence) | yupana | yupana_impact / whatif |
| Named stored queries for this suite | quipu | the quipu #79 pattern |
This is policy-by-example generalized from point exemplars (one observed edit) to trajectory exemplars (one observed success): the same four-step gesture — point, draft, backtest, born-advisory — applied to a sequence instead of a hunk.
7. Related work
The blessing ladder's nearest published relative is ActiveGraph (Nakajima, The Log is the Agent: Event-Sourced Reactive Graphs for Auditable, Forkable Agentic Systems, arXiv:2605.21997): an event-sourced agent runtime whose fork→test→promote loop is this design's promotion arc with the same auditability motive — every mutation traces to its events, runs replay deterministically, and hypotheses are cheap forks. The convergence is real and worth citing; the difference is the evidence bar. ActiveGraph's lineage records what happened; this design additionally demands what happened earn its standing — falsifier-gated verification before admissibility, cone-checked pruning, a backtest before a rule is born, and a recorded human act before anything enforces. Its fork-at-any-event ergonomics are worth adopting on quipu's speculate machinery (filed as quipu-gp5), with one constraint stated up front: a promoted fork re-enters through the write gates, so fork convenience never becomes a gate bypass.
8. What this slice refuses to do
- No stored "still good". A path's current fitness (Q16) is a read-time judgment over conformance evidence; only promotion events are stored.
- No model-asserted blessing. Similarity suggestions and drafted candidates may be model-produced, but they land as inferred-plane facts, tagged, and nothing above L1 happens without a recorded human act.
- No second promotion machinery. Levels ride quipu's existing gates.
- No L5 before signing. Constraint-backing authorization waits on signed verdicts; until then the ladder honestly stops at L4.
Design: certified knowledge packs
Implementation status (2026-08-29): 🟩 Producer certification and digest-addressed distribution built. Quipu implements
.pendant.dbcreation, deterministic content hashing, verification, attach/import, and theCertifiedShareBundleSHACL envelope. Camayoc'scertify_pack.pyinvokes pack+verify, reads the authoritative manifest hash, and emits the separate publisher/certifier claims plus external-truth mapping. It verifies distinct Ed25519 keys, computes the hash from a conforming JSON SHACL report, and scans the complete SQLite artifact before asserting scrub success. The RML executor remains a separate acceptance gate.
Camayoc has one distribution artifact, not a Camayoc pack beside a Quipu share
bundle. A Camayoc knowledge pack is a Quipu CertifiedShareBundle; its
physical archive encoding is .pendant.db.
Naming. Quipu calls its share artifact a pendant (formerly "qpack").
On a khipu, pendant cords hang from the primary cord and each carries a
self-contained record: the primary cord is a store, a pendant is a shareable
graph with its shapes and stored queries, subsidiary cords are delta shares,
and relationships are knots between cords. For one release, a durable root's
existing sha256/<digest>.qpack.db objects are still read; new publications
are written only as .pendant.db.
1. Normative artifact contract
Quipu owns the bytes and their identity:
quipu packwrites the.pendant.dbSQLite artifact.pack_manifest.content_hashis the canonical graph hash: SHA-256 over the lexically sorted, deduplicated N-Triples of graph, shapes, stored queries, and labels.- The same content hash names the artifact in git, OCI, Garage, and the graph. Upload location and filename are copies, never new identities.
quipu pack --verifyestablishes integrity only. It does not establish who published the pack or whether its knowledge passed certification.
Camayoc owns the certification envelope. One RDF node typed
aegis:CertifiedShareBundle carries exactly one canonical graph hash, shapes
bundle version, provenance manifest, publisher attestation, and certification
seal. The two signatures are separate claims:
aegis:PublisherAttestationbinds the publisher's registered key and signature to the bundle.aegis:KnowledgeCertificationSealbinds an independent certifier's registered key and signature to the same canonical graph hash, the shapes version, SHACL report hash, passing outward-scrub result, and provenance manifest.
Both keys are existing aegis:VerifierRegistration nodes. A Shuttle-derived
bundle additionally binds the seal to the frozen operational-window IRI.
Static packs do not invent a window merely to satisfy the generic envelope.
2. Production and verification sequence
The producer MUST execute these steps in order:
- Close the source graph or Shuttle window to further writes.
- Run
quipu packwith explicit shapes and stored queries. - Read
pack_manifest.content_hash; never compute a competing identity. - Run
quipu pack --verifyagainst the emitted file. - Run SHACL validation and the outward-share scrub over the bounded content.
- Emit the publisher attestation and independent certification seal over the same hash and manifest inputs.
- Validate the complete RDF envelope against Quipu's loaded governance shapes.
- Store the file under a content-addressed Garage key and record an
external-truth mapping (
source_uri,access_via,freshness,verified_by) on the bundle entity.
A consumer given only the bundle IRI resolves the mapping, fetches the bytes, recomputes the manifest hash, verifies both registered signatures, verifies the SHACL and scrub evidence, and only then attaches or imports. Import remains Quipu's quarantined staging flow; certification never grants implicit promotion.
Garage is the durable cold copy, not the trust root. A successful upload proves only storage acceptance. The manifest hash proves content identity; the two registered signatures prove publisher and certifier claims. Retention, bucket policy, and credential custody are deployment inputs and MUST be audited before the first production upload.
The implemented producer boundary is:
scripts/certify_pack.py <graph-iri> --db <source.db> --out <name.pendant.db> \
--name <name> --version <version> --shape <shape-set> --query <query-name> \
--shacl-report <conforming-report.json> --publish-dir <durable-root> \
# plus two distinct public-key/key-IRI/signature inputs and mapping fields
It executes Quipu through argument arrays (never a shell), runs pack --verify,
and reads pack_manifest.content_hash read-only. A Shuttle-derived invocation
without --frozen-window-iri refuses. Signatures use Quipu's Ed25519 hex
convention and cover domain-separated canonical claim messages. The producer
refuses a bad signature, reused key identity, non-conforming report, mismatched
report hash, or artifact scrub finding; callers do not supply the scrub boolean
or SHACL hash. Publication is atomic at
<durable-root>/sha256/<manifest-digest>.pendant.db; source_uri must identify
that exact content-addressed copy.
3. Relationship to the Quipu v1 share interface
The aegis-33cgl v1 share directory and a Camayoc .pendant.db are two physical
projections of one governed semantic object, not competing knowledge formats:
.pendant.dbis the single-file, directly attachable Camayoc distribution artifact. Itspack_manifest.content_hashis the bundle'scanonicalGraphHash.{manifest.json, export.nt, shapes.ttl}is Quipu's git-native projection for review, lineage, diff, and/import. Itsshare_ididentifies that transport envelope and parent chain; it never replaces the canonical graph identity.- Camayoc invokes Quipu to create either projection. It does not reimplement canonical RDF serialization, share-manifest hashing, resolution, or quarantine.
manifest.producer.name = "quipu"identifies the canonicalizer. The publisher key identifies authorship, and the independent certifier key identifies conformance; those three roles must not be collapsed.
Consumers preserve every v1 manifest field and follow Quipu's import contract: hash/version/path mismatch refuses before parsing; exact entity matches may be rewritten locally; fuzzy matches remain review candidates; non-conforming or off-vocabulary facts remain quarantined; certification never promotes directly to ROOT. The certified bundle's external-truth mapping is the discovery path to the digest-addressed artifact, while the v1 share directory is the composition path. Both name the same bounded source and named shapes version.
Garage/S3, git, and OCI are storage or transport adapters over the digest key.
They are not trust roots, do not mint new bundle identities, and do not change
the signed claims. The producer implements both an atomic filesystem adapter
and an S3-compatible adapter. Both preserve the exact
sha256/<digest>.pendant.db key contract; the S3 path verifies retained size and
canonical-hash metadata with a separate HEAD before recording its URI.
4. Declarative ingress: the Camayoc RML subset
Camayoc also owns the executor for declarative source-to-graph mappings because this is knowledge ingress, not storage. Mappings are RDF data governed in Quipu and may travel inside the certified pack above. The normative executor contract is rml-executor.md; this section only fixes its relationship to pack production.
The first implementation is a principled materializing subset:
- logical sources: JSON, CSV, and SQLite;
- iterators: JSON paths, CSV rows, and one parameterized SQLite query;
- term maps: constant, reference, and template;
- subject maps and predicate-object maps;
- explicit target type, target graph, source kind, and mapping IRI;
- output through Camayoc's governed Quipu write lane with the mapping IRI as provenance;
- deterministic ordering and stable entity names, so executing the same mapping twice is byte-identical and idempotent.
The vocabulary stays aligned with RML/R2RML so a mapping remains portable. Camayoc does not claim spec completeness. Version 1 excludes joins across logical sources, custom functions, XML, streaming sources, and virtual query-time federation. Unsupported terms refuse before reading or writing a source; they never degrade to a best-effort interpretation.
The external-truth quartet delivered by aegis-jsyl2 is a source-discovery
contract, not executable transformation logic. An executable mapping adds
standard RML/R2RML triples-map data and refers to a governed external-truth
subject. Quipu's loaded vocabulary does not yet include those standard mapping
classes, so loading the Camayoc RML subset shapes is a prerequisite, not work
the executor may silently route around.
The first conformance fixture is a bounded bead-store slice. One declared
mapping must materialize it twice with the same entity names and episode body,
produce one logical result, and name the mapping IRI in provenance. Subsequent
fixtures cover services.json and Prometheus scrape targets.
5. Interfaces and ownership
| Concern | Owner | Interface |
|---|---|---|
| Pack bytes, manifest hash, attach/import | Quipu | .pendant.db, pack_manifest, pack CLI |
| Meaning, mapping vocabulary, ingress policy | Camayoc | RDF mappings + governed shapes |
| Publisher signature | Producing principal | PublisherAttestation |
| Knowledge certification | Independent certifier | KnowledgeCertificationSeal |
| Operational source windows | Shuttle | Frozen-window IRI |
| Durable byte storage | Garage | Content-addressed object key |
There is no second pack class, manifest hash, archive extension, or signature
shortcut. In particular, a Shuttle TransitionEvent signature cannot stand in
for either the publisher or certifier claim: it authenticates a transition,
not the finished bounded artifact.
6. Acceptance gates
- A static pack and a Shuttle-window pack both conform to the loaded Quipu certification shapes; removing either signature or setting scrub pass false is refused.
- One source graph produces one
.pendant.db; its manifest hash is unchanged by relocation through git, OCI, and Garage. - An independent consumer fetches from only the graph mapping, verifies hash and both claims, then imports into quarantine.
- The bead-store mapping executes twice byte-identically and yields one logical result with provenance naming the mapping IRI.
- Unsupported RML features fail before any Quipu write.
Design: prose entity mentions — deterministic, still quarantined
Implementation status (2026-08-31): ✅ Implemented as
scripts/extract_entities.py(camayoc-0c8, gap G4 of the txtai comparison, caboodledocs/design/txtai-gap-analysis.md).tests/test_extract_entities.pypins the promises below.
txtai ships NER pipelines; camayoc's ingress rule 4 already defines the slot —
inference is quarantined, not banned. This producer fills it with no model
in the loop: mentions come from (a) a gazetteer of the graph's own labels
(rdfs:label, skos:prefLabel, skos:altLabel, fetched via SPARQL) and
(b) explicit user-supplied regex patterns, each declared with the entity IRI
it stands for.
Why deterministic output still lands in crew:inferred
The extraction is reproducible, but the claim — "this prose is about that
entity" — is a reading of the text, not a record of an event. A string equal
to a label is evidence, not identity. So every mention routes through
planes.plane_for("inferred"), tagged aegis:sourceKind "inferred" — never
ROOT, never an observed plane — and can earn promotion the governed way
(scripts/promote_plane.py) like anything else in quarantine.
Abstentions, counted out loud
- A gazetteer label naming two entities matches for nobody — a wrong mention silently attaches prose to the wrong referent, worse than a gap.
- Labels under 3 characters are skipped (they match everything).
- Matching is whole-word and case-sensitive; case-folding is a guess this producer does not make.
- Fenced code blocks are not prose.
Every abstention is counted on stderr, alongside the gazetteer size — the ingest-git lesson that a silent abstention is indistinguishable from coverage.
No new ontology terms
The competency rule is not triggered: a mention is a generic fact over
existing predicates — aegis:about (the matched entity), aegis:identifier
(the surface text), aegis:filePath (where), rdfs:label (line and section,
human-readable), aegis:sourceKind. No class is minted for it; if mentions
later need their own query surface, that is the moment a competency question
earns one.
Refusals
An unreachable store, a gazetteer that cannot be read, an unprovisioned
inferred plane, or a plane registered but not labelled all exit nonzero
before anything is written — could not look is not zero mentions, and a
registered-but-unlabelled plane is quarantine's appearance without its
substance (scripts/planes.py). Zero mentions writes nothing at all: the
writer is never called, not called with an empty body. --dry-run prints the
would-be triples (sorted N-Triples; byte-identical on re-run) and writes
nothing.
Design: governed RML-subset execution
Implementation status (2026-08-31): ✅ The bounded version-one executor is implemented and verified end to end, and version two adds referencing object maps (camayoc-5bf, from the Spanner Graph investigation in quipu's
docs/design/spanner-capabilities.md§4.1):rr:parentTriplesMap+rr:joinConditionare admitted — same-source joins read the child's own records, cross-source joins load the parent's logical source via--parent-source-file <logical-source-iri>=<path>, joins hash on the string form of values, unmatched joins emit no triple but are counted in the invocation report (unmatched_joins), and a join condition is required even same-source. Functions, dynamic predicates/graphs, and logical targets remain excluded. The v1 baseline: it fetches a mapping closure from Quipu, preflights it before source access, reads file-backed JSON/CSV/SQLite, emits deterministic N-Quads, and submits the result to a registered named graph through Quipu's SHACL-governed/knotlane. The live fixture committed 14 conformant triples and a repeat execution read back the same 14-row graph.
Camayoc owns transformation from structured external truth into candidate RDF. Quipu owns mapping storage, governance, and graph writes. The executor is a bounded, deterministic implementation of RML 1.1.2 concepts and the W3C R2RML term-map model; it is not a new mapping language and does not claim full RML conformance.
1. Two contracts, deliberately separate
The four properties delivered by aegis-jsyl2 describe how to locate and
verify external truth:
aegis:source_uriaegis:access_viaaegis:freshnessaegis:verified_by
They do not say how source records become RDF terms. An executable mapping is
an rr:TriplesMap with an rml:logicalSource, one subject map, and zero or
more predicate-object maps. The triples map or its logical source links to the
governed external-truth subject; it never copies credentials into mapping RDF.
The executor MUST reject either half in isolation:
- an RML triples map without one complete external-truth pointer cannot fetch;
- an external-truth pointer without a triples map is discoverable but not executable.
The four live jsyl2 records are compatibility fixtures for pointer resolution, not evidence that four executable mappings already exist.
2. Governance prerequisite
Before executable mappings are written to Quipu, its loaded vocabulary MUST admit and constrain the standard classes used by this subset:
rr:TriplesMap,rr:SubjectMap,rr:PredicateObjectMap,rr:ObjectMap;rml:LogicalSource;- the term-map and logical-source properties listed below.
The persistent source belongs in a loaded Camayoc shape set. A pending
/propose record alone is not sufficient. Until the loaded /shapes
vocabulary response exposes those classes, Camayoc may parse mapping fixtures
from files for tests but MUST refuse a production execute request.
The shapes enforce the subset structurally: exactly one logical source and subject map per triples map, exactly one source and reference formulation per logical source, and exactly one of constant/reference/template on every term map. Unsupported standard terms are rejected by Camayoc's semantic validator before any source access.
3. Version-one vocabulary
Camayoc reads the established namespaces directly:
rml: http://semweb.mmlab.be/ns/rml#
rr: http://www.w3.org/ns/r2rml#
ql: http://semweb.mmlab.be/ns/ql#
Supported logical-source terms:
| Term | Version-one meaning |
|---|---|
rml:logicalSource | Exactly one logical source per triples map. |
rml:source | IRI of the governed external-truth subject, not a secret-bearing connection string. |
rml:referenceFormulation | Exactly one of ql:CSV, ql:JSONPath, or rr:SQL2008. |
rml:iterator | CSV row (omitted or row), bounded JSONPath, or absent for SQL. |
rml:query | One read-only, parameterized SQLite SELECT; no stacked statements or mutation. |
Supported term-map terms:
| Term | Constraint |
|---|---|
rr:constant | IRI or literal copied exactly. |
rml:reference | CSV column, JSONPath relative to the current record, or SQL result-column name. |
rr:template | Text with {reference} substitutions; every reference must resolve. |
rr:termType | rr:IRI or rr:Literal; blank nodes are excluded. |
rr:datatype | Constant datatype IRI on literal object maps. |
rr:language | Constant BCP 47 language tag on literal object maps. |
rr:class | Emits rdf:type for the generated subject. |
rr:predicateObjectMap | Constant predicate plus one supported object map. |
rr:graph | One explicit target named graph for the triples map. |
Version one excluded referencing object maps and joins; version two admits
them (camayoc-5bf). A rr:RefObjectMap carries exactly one
rr:parentTriplesMap (a triples map in the same closure, with its own subject
map and logical source) and at least one rr:joinCondition, each with exactly
one rr:child and one rr:parent reference — the join is required even when
both maps share a logical source, because an implicit identity join would make
edge semantics depend on which source two maps happen to share. Join
evaluation is a hash join on the string form of values (a CSV "1" meets
a SQLite 1), the object emitted is the parent map's generated subject (must
be an IRI), and an unmatched join emits no triple — standard R2RML — but is
counted and reported as unmatched_joins so the silence is visible. Same-source
joins read the child's records; a cross-source join requires the parent's
logical source to be an IRI and its file supplied with
--parent-source-file <logical-source-iri>=<path>, loaded under the same
byte/row bounds as any source.
Still excluded: multiple logical sources per triples map, custom functions, inverse expressions, dynamic predicates or graphs, XML, remote SQL servers, SPARQL federation, streaming inputs, and RML logical targets. Encountering any excluded term is a validation error, never a warning.
4. Execution state machine
One invocation progresses through five observable phases:
resolve mapping -> validate plan -> open bounded source -> materialize -> commit
- Resolve mapping. Fetch the triples map and its bounded RDF closure from Quipu. Resolve the referenced external-truth subject and require all four jsyl2 properties. Ambiguous or missing values refuse.
- Validate plan. Parse the whole mapping, reject unknown/unsupported terms, validate the base IRI and target graph, and compile every iterator, reference, template, and SQL statement. This phase performs no source I/O.
- Open bounded source. Dispatch by both
access_viaand reference formulation. Version one accepts only allowlisted file paths and applies byte and row limits before parsing. Mapping RDF never carries credentials. - Materialize. Iterate records in source order, generate RDF terms, sort the resulting quads lexically by N-Quads representation, and deduplicate exact duplicates. Missing required references fail the invocation; they do not silently omit a triple.
- Commit. Convert the deterministic quads to Turtle and submit them once
through
/knotto the mapping's registered target graph. The request source names the mapping IRI, mapping-closure hash, external-truth subject, and verified source hash. HTTP refusal and indeterminate transport failure are distinct machine-readable outcomes.
validate runs phases 1-2. plan runs phases 1-3 and reports bounded source
metadata without emitting values. execute --dry-run runs phases 1-4 and
writes canonical N-Quads plus the episode body to stdout. Only execute
enters phase 5.
5. Source adapters and safety
The executor selects an adapter from the pair (access_via, referenceFormulation), not from a filename suffix.
| Source | Required pair | Boundary |
|---|---|---|
| CSV | file + ql:CSV | Header required; duplicate headers refuse; RFC 4180 parsing; bounded rows/bytes. |
| JSON | file + ql:JSONPath | UTF-8 JSON array; $[*] only; bounded rows/bytes. |
| SQLite | file + rr:SQL2008 | Read-only URI, one SELECT, bound parameters only, progress-handler deadline. |
Version one does not execute the live promql pointer. It remains a pointer
resolution fixture until a result-table adapter is specified. Likewise, an
HTTP response such as Garage's expected 403 auth boundary verifies a pointer
but is not mapping input.
Remote fetches refuse private or loopback destinations unless the mapping policy explicitly names an approved resolver. Redirects are re-checked at every hop. File sources must resolve beneath configured allowlisted roots. SQLite opens immutable/read-only and cannot attach another database. Logs may contain mapping IRIs, counts, hashes, and error classes; they never contain authorization headers, source rows, or generated literal values.
6. Determinism and identity
For identical mapping closure, source bytes, base IRI, and executor version:
- generated RDF terms and canonical N-Quads are byte-identical;
- the episode name, source, node order, edge order, and description strings are byte-identical;
- rerunning produces Quipu's idempotent
unchangedoutcome or an equivalent no-op after read-back; - provenance names the triples-map IRI, mapping-closure hash, external-truth subject IRI, verified source identity, and executor version.
Templates percent-encode substituted values when producing IRIs. Relative IRIs require one explicit base IRI. Locale, wall-clock time, filesystem order, hash-map order, and network response ordering cannot affect output. A mapping that needs "now" must receive it as declared source data; the executor never invents it.
7. Public interface
The first CLI surface is intentionally narrow:
scripts/rml_executor.py validate <triples-map-iri> [--mapping-file FILE]
scripts/rml_executor.py execute <triples-map-iri> --source-file FILE [--mapping-file FILE] [--dry-run]
[--parent-source-file <logical-source-iri>=<path>]...
scripts/rml_executor.py freshness <triples-map-iri> --source-file FILE --server URL
scripts/rml_executor.py remap <triples-map-iri> --source-file FILE --server URL [--actor A] [--dry-run]
freshness (quipu-212) reads the target graph's last-materialization stamp
from quipu's GET /graphs (served from transaction provenance, so it cannot
drift from what committed) and judges: never_materialized, stale
(source_hash_changed, or window_elapsed when the declared
aegis:freshness is a machine-readable max_age(N[smhd]) — any other
declaration contributes nothing and the hash comparison stands alone), or
fresh. remap acts on the verdict: fresh is a deliberate no-op (the same
bytes would only reach quipu's idempotent unchanged), stale or
never-materialized falls through to the ordinary execute path — which is
what makes over-triggering remap harmless. The verdict is a read-time
judgment; the executor still never invents "now" for materialized output.
Production mode resolves mapping RDF from Quipu. --mapping-file exists for
conformance tests and bootstrap before the RML shapes are loaded; non-dry-run
execution with it is refused. JSON output includes phase, mapping IRI, source
identity, mapping/source hashes, input/output counts, target graph, and a
machine-stable error code.
Error families are mapping_not_found, mapping_ambiguous,
mapping_ungoverned, unsupported_term, invalid_term_map,
source_policy_refused, source_unreachable, source_changed,
materialization_error, write_indeterminate, and write_refused.
8. Conformance fixtures
The first end-to-end fixture is a bounded JSONL export of bead records. It is
checked into tests/fixtures/rml/ with:
- one RML Turtle mapping;
- source bytes and SHA-256;
- expected sorted N-Quads;
- expected deterministic episode JSON;
- one malformed mapping per refusal arm.
The acceptance test executes the valid mapping twice and requires byte-equal N-Quads and episode JSON, one logical set of output facts, and provenance that names the triples-map IRI. Negative tests cover incomplete jsyl2 pointers, multiple term-map constructors, missing references, traversal outside an allowlisted root, non-SELECT SQL, unsupported joins, unknown formulations, and a failure before the mocked write endpoint receives any request.
Companion fixtures exercise CSV and SQLite and require byte-equivalent quads.
The live integration fixture is
https://camayoc.dev/mapping/aegis-07hmc.3-fixture; its target is a dedicated
registered fixture graph rather than a production plane.
9. Implementation sequence
- Land and load the Camayoc RML subset shapes; verify the server vocabulary and one accepted plus one refused mapping write.
- Add pure mapping-RDF parser and semantic validator.
- Add term generation and canonical quad emission with fixture tests.
- Add bounded JSON, CSV, and SQLite adapters.
- Add Quipu mapping retrieval and existing Camayoc plane/write integration.
- Run the bead-store fixture twice against a disposable Quipu store, then one governed mapping end to end against the live interface.
Pack production is downstream of successful materialization: mappings and
their shapes may be carried in the same certified .pendant.db, but packaging
does not weaken or bypass this executor's validation and source-policy gates.
References
Request cost attribution
Status: implemented locally; scheduled delivery and live acceptance pending.
Q16 now asks for per-work model and disjoint token counts. Camayoc owns the arithmetic. Shantytown supplies versioned focus intervals containing the rig, bead, agent, session, start, end and boundary evidence. Neither global assignment nor a session-wide bead guess is allocation evidence.
retrieve_metric.py accepts the session_usage system and work_cost query,
with params.sources (path, harness, agent) and params.bindings. It parses
Claude request identities and Codex response identities, including compaction.
Identical repeated rows count once; conflicting rows abstain. Missing models or
counts stay unknown. Reasoning and cache TTL details are retained as metadata.
Intervals are half-open. A request matching no interval or conflicting intervals remains unattributed. Repeated identical bindings do not double-charge a request. Totals describe the supplied sources, never an unmeasured lifetime cost.
publish_work_cost.py uses that same parser and replaces only its own session
snapshot in the observed plane. Request IRIs retain the existing usage producer's
identity. The existing inSession and attributedTo edges join requests to
sessions and canonical WorkItems. The tracker producer must have populated those
WorkItems first. No separate bead twins or new session predicate are introduced.
The optional breakdown shapes travel with the producer's write. They extend UsageRecord; no new entity class is needed. Reads store no metric sample. USD is not estimated without a governed applicable price record.
Rollback disables the scheduled projection and retains evidence. It does not rewrite tracker close reasons or silently delete prior cost observations.
Cost metric source groups
Status: source-aware publisher implemented; coordinated rollout proof pending.
Active-source exposition can carry a # st-bead-cost-source JSON comment with
the parser's agent, harness and session identity. publish_cost_metrics.py
validates that identity before pushing anything. It sends token and coverage
samples to a Pushgateway group keyed by rig/agent/harness/session, and scheduler
status to the existing rig-only group. Rotation preserves earlier sessions;
corrected attribution replaces only the affected session's allocations.
Identity and counts travel in one atomically replaced exposition file, avoiding
a crash mismatch between separate count and identity files. Cached review and
cooldown refreshes retain the source identity and original observation time.
st_bead_cost_source_last_success_timestamp_seconds reports that source's
observation age; the rig heartbeat does not establish fresh cost observations.
Unmarked payloads retain the legacy rig-only publication contract.
Install this publisher before enabling source markers. Each marked metric publication makes two gateway PUTs, one sample group and one status group; this does not change graph requests, snapshot limits, or token arithmetic. Both PUTs must succeed for the caller to report success. The separate groups are not a gateway transaction; a partial failure is reported for reconciliation. Totals remain scoped to observed supplied sessions, not unmeasured lifetime cost.
First-rollout load budget
Each invocation permits at most one changed session snapshot, eight distinct
attributed WorkItems, 1,000 requests and 4 MiB of serialized snapshot body.
Exceeding any limit refuses before network access; partition backfills explicitly.
Unchanged snapshots issue no request. The changed snapshot makes at most three
requests: one bounded VALUES SELECT preserving an asserted-only FILTER, one
snapshot write, one read-back. Starts are spaced at least one second apart.
The initial schedule must be no more frequent than once per minute. Every cost
request carries X-Quipu-Client: camayoc-cost; other plane helper callers use
camayoc-ingress or camayoc-planes. Raising these limits requires a reviewed load budget.
Read-back checks the sampled request's exact attribution set as well as its total, so a corrected focus retaining an old WorkItem edge leaves the pending marker unresolved instead of reporting success. This samples one request, not the entire snapshot; rollout acceptance must include a deliberately re-attributed request.
Resilience gate and recovery
The caller label is required: cost sends camayoc-cost, tracker ingress sends
camayoc-ingress, and plane/window administration sends camayoc-planes.
Preflight uses one bounded VALUES SELECT and asserted-type FILTER. A partial
result names missing IDs. Zero rows trigger one single-item control and return
INDETERMINATE, never an instruction to repeat ingress. A 408 or transport timeout
aborts before writing.
Every publication attempt writes a sibling .receipt.json and .prom next to
its state, and emits the receipt to stderr. Receipts distinguish OK, UNKNOWN,
STALLED and OPERATOR_ACTION, list observed items per changed snapshot, and name
read-back as a last-record sample. The scheduled wrapper must surface nonzero
exit to the coordinator and retain stderr; an unmonitored cron logfile is not
an acceptable schedule. The status metric file is available for a textfile
collector, but its existence alone does not establish a scrape.
Calls are spaced one second apart and capped at three per attempt. Persisted receipts enforce at least sixty seconds between network-bearing attempts. A write taking over ten seconds, or summed request wall time over fifteen seconds, sets a fifteen-minute cooldown. Request wall time is a conservative budget proxy; it is not measured server lock occupancy. Cooldown survives restart.
A pending marker means the write may have landed. It contains the exact canonical JSON body sent on the wire. Automatic runs preserve it and report STALLED; they never retry that write. Franklin (or the coordinator's explicitly assigned recovery operator) owns reconciliation:
- Stop the scheduled cost publisher while retaining the marker, state, receipt and source inputs. Record their SHA-256 values on the task.
- Inspect the stored Turtle for the snapshot identity and request facts. Query one previously known canonical WorkItem using the asserted-type FILTER as a control. If the control fails or reads time out, leave the marker in place.
- Read the marked snapshot's expected request totals and attribution sets, including the corrected assignment if this was a correction. Repeat after a gap. An extra attribution edge is a mismatch, not success. A sample is only a sample; record precisely which requests were checked.
- If reads prove the expected snapshot, write its SHA-256 (of the marker bytes)
under the marker's
snapshotkey in the local state atomically, then remove the marker. This is the same cursor/digest format as normal publication. Retain the evidence on the task before resuming. - If two controlled reads show absence, a recovery operator may replay the
unchanged marker bytes to
/knotwithX-Quipu-Client: camayoc-costand write authorization. Respect the recorded cooldown and at most three requests per minute. Do not regenerate it from a growing transcript or edit its wording. Acceptance still requires the controlled read-back in step 3; another timeout leaves the same marker and the schedule stopped.
Narrow sources when a snapshot exceeds eight items, one thousand requests or four MiB; do not silently drop records to fit. Report the first scheduled run's actual items-per-snapshot distribution before asking to enlarge that budget.
Twenty-shape budget review
Receipts always carry items_per_snapshot, records_per_snapshot and
body_bytes_per_snapshot; empty lists mean no snapshot dimensions were measured,
not a zero-sized measured snapshot. During cooldown the prior dimensions are
retained with dimensions_source: previous_attempt. A budget refusal emits
camayoc_cost_projection_stalled{reason="budget"} 1; pending writes use
reason="pending", transport/preflight failures use reason="error", and normal
cooldown uses reason="backoff" with value zero. projection_ok distinguishes
intentional cooldown from a successful attempt.
The sibling .budget.json retains maximum observed items, records and bytes,
including budget refusals. Its run count advances only when a changed snapshot
reaches canonical preflight. Repeated runs of one shape do not provide a
distribution: the review becomes due only after twenty distinct
(items, records, body_bytes) tuples have reached preflight.
camayoc_cost_preflight_runs retains the run count;
camayoc_cost_distinct_shapes exposes distinct progress and
camayoc_cost_budget_review_due reports the twenty-shape gate.
The reviewer re-rules from the maximum, never the median.
A hand-selected two-item pilot proves one input only and does not discharge this
review. Limits remain enforced until an explicit new budget is reviewed.
The first twenty run samples stay unchanged for historical comparison.
distinct_samples retains the first twenty different measured tuples, with
their first retained run and timestamp. Both collections are bounded; the
distinct count saturates at twenty and is a lower bound thereafter, explicitly
marked by distinct_shapes_saturated. These are early-life observations, not
a recent distribution. Lifetime maxima continue increasing after saturation,
including dimensions from budget refusals. Such refusals do not advance the
preflight sample population.
Old measured samples seed the distinct collection, but historical maxima cannot
reconstruct missing samples. Migration also runs on unchanged-snapshot ticks,
so an old run-based review_due cannot remain green indefinitely. This changes
the follow-up review instrument only, not source selection or publication caps.
Snapshot budget samples include unattributed request populations: zero proven WorkItems is a measured dimension, not zero workload. These snapshots pass the same local size caps and have no canonical WorkItem query; their knot/read-back requests still count toward the unchanged request and cooldown budgets.
After the bounded population completes, publish_work_cost.py --review-only
refreshes native health/review metrics without reading a method or contacting the
graph. It refuses an incomplete population or unresolved pending write, and
preserves the measured history. Display paths never invoke this mode.
Refusals and review populations
Budget accounting is stored in refusal_accounting in the budget history and
publication receipt. attempts counts one changed invocation reaching a size
budget decision; refusals counts those refused before graph network access.
Backoff, unchanged snapshots, paused review refreshes, missing authority and
invalid source reads do not count as size decisions. An admitted decision can
still fail later on canonical-item lookup, transport or read-back; admission is
not successful publication.
limits counts each binding dimension on the first rejected snapshot (items,
records, body_bytes), or snapshots when the completed candidate collection
exceeds that cap. A refusal can bind multiple dimensions, so the sum of limit
counts is not a denominator. No claims are made about unexamined later snapshots.
The latest twenty refusal receipts retain source agent, harness, session and
snapshot identity plus measured dimensions. Each retained entry limits source
and dimension lists to two snapshots and reports source truncation. Lifetime
counters remain exact for observed decisions even when old detailed evidence
ages out. Transcript bodies are never retained in this accounting.
Prometheus exposes camayoc_cost_budget_attempts_total,
camayoc_cost_budget_refusals_total, and
camayoc_cost_budget_limit_refusals_total{limit="..."}. They are lifetime counters
for this state file, starting at
camayoc_cost_budget_accounting_since_timestamp_seconds. Historical refusals
before that timestamp are unknown, not zero. Preserve refusal_accounting
when archiving and clearing an accepted bounded shape population.
The existing review_window_started_at marker sets the next window boundary.
On its first observed tick, accounting records lifetime baselines and an
observed_since timestamp; window attempt/refusal gauges subtract those baselines.
The timestamp gauge camayoc_cost_budget_window_observed_since_timestamp_seconds
is essential when instrumentation began partway through a window. A window whose
start precedes observed coverage has incomplete refusal evidence; report the gap.
A deleted state file starts a new lifetime, visible through a new start timestamp
and counter reset. Refusal totals cannot reconstruct the missing history.
At the next review, read the receipt's budget history alongside the existing
admitted distinct_samples, source rotation evidence and metric timestamps.
The distribution is censored at the unchanged caps: its maximum describes the
largest admitted sample, not headroom. Exact shape equivalence remains
(items, records, body_bytes). Repeated admitted shapes increase attempts without
necessarily increasing distinct shapes; that alone does not prove pinned input.
Scheduler attempts before source selection
--scheduler-only tick --publish-status refreshes the attempt timestamp without
reading sources or querying the graph. It preserves the previous outcome,
request cooldown, pending write body, and budget history; a heartbeat is not a
successful projection. The scheduler calls it under its publication lock before
checking pending work, review pauses, cooldowns, or selecting an active source.
--scheduler-only source_selection (or preflight / projection) records an
early UNKNOWN outcome and its bounded reason. The existing failure and stale
alerts can then distinguish repeated failed attempts from missing reports.
camayoc_cost_projection_unknown{reason="source_selection"} identifies the
no-source path. Status-only calls neither read transcripts nor write graph data.
Deploy this publisher interface before a scheduler that invokes it.
Design: Jev — typed decisions in the ingress path (exploration)
Status: EXPLORATION, FIRST ARM BUILT (2026-09-21). Stiwi has a TypeSafe account (console.typesafe.ai, created 2026-09-20). Slot 2 below ships as
competency.py --method jevoverscripts/jev.py; nothing is decided. This doc exists so the question stays open in the repo rather than in a chat. It records what Jev is, where a typed decision would slot into camayoc and into NeuralAmplifier, what the ingress discipline demands of it, and what has to be true before the first experiment runs. Tracked on the aegis board; see the bead named at the bottom.
1. What Jev is (measured from public sources, not from use)
Jev is TypeSafe AI's first "System One" model (announced 2026-09-15). It
generates no text. A request carries a state (string, object or array) and a
map of named questions; the reply carries every answer in one parallel pass.
| Primitive | Input | Output |
|---|---|---|
noul | a yes/no proposition | one probability in [0,1] |
choice | up to 255 named options | winner, probability per option, confidence |
score | 2–10 ordered rubric levels | probability-weighted position, distribution, confidence |
POST https://api.typesafe.ai/v1/systemone, bearer key, model jev-latest.
Questions in one request are evaluated independently. Public figures: 70–500 ms
per call; input $0.042 per million tokens, output unmetered; ~$0.0004 per
decision. TypeSafe's classification benchmark: Jev 67.8% vs Claude Opus 5 73.1%.
Langfuse's 6,003-check eval benchmark: 91.5% agreement with Claude Fable 5.1 at
$160 per million answers vs $33,000. Early access behind a waitlist as of
2026-09-20.
Three properties shape every use below:
- It cannot abstain. A
choicewith no fitting option still picks one. Every choice we pose carries an explicit "none of these" option, and a confidence floor decides whether the answer is used at all. - It gives no rationale. An auditable decision must reconstruct its trace from the state and the question, which is why the verdict record must carry both verbatim.
- It degrades on long, noisy state ("context rot", Langfuse). State is assembled by deterministic code, minimal, and its size recorded.
2. What the ingress discipline demands
A Jev answer is a model judgment. Ingress rule 4 applies without exception:
- Every verdict is tagged
aegis:sourceKind "inferred"and routed throughplanes.plane_for("inferred"). Never ROOT, never an observed plane. - The verdict record carries
method: "jev-latest",semantic: true, the question text, the criteria as posed, the raw probabilities, the confidence, the threshold used, and the state watermark — the same honestysettled_decisions.pyandcompetency.pyalready enforce on their lexical scorers. A lexical verdict may not masquerade as semantic; a semantic one may not hide its numbers. - Jev never promotes. Plane promotion stays behind
config/plane-authority.jsonandscripts/promote_plane.py. Jev may rank the promotion queue for a human; it holds no authority. - Routing by
sourceKindstays deterministic. No model decides which plane a fact enters.
3. Candidate uses in camayoc, ranked by fit
| # | Slot | Today | Jev shape | Fit | Why |
|---|---|---|---|---|---|
| 1 | Settled-decision collision (settled_decisions.py, camayoc-7lt) | lexical, advisory, says so | one noul per standing decision: "does the new Decision duplicate or conflict with this one" | strong | advisory posture already tolerates error; verdict shape exists |
| 2 | Question mapping onto the competency suite / NO COVERAGE (competency.py, camayoc-b6h) | lexical; embedding path blocked by network policy (HF CDN denied) | choice over parsed competency questions + "none of these"; confidence floor → NO COVERAGE | strong | the semantic matcher b6h asked for, without weights on disk |
| 3 | Promotion-queue triage | nothing ranks the quarantine plane | score "how well evidenced is this fact" over a fixed rubric | good | orders human review; authority untouched |
| 4 | Entity-mention disambiguation (extract_entities.py) | abstains on a label naming two entities | choice among the candidates + "neither" | cautious | doc's stance: a wrong mention is worse than a missing one; keep the abstain path, high floor |
| 5 | Plane routing (planes.plane_for) | deterministic | — | no | must stay deterministic |
4. Candidate uses in NeuralAmplifier
NeuralAmplifier's value is a brain that explains its moves; Jev gives no reasons, so the LLM-tier action choice is the wrong first target. Three other slots:
- Grounding-fact ranking — the open na-htm question is whether ranking facts
by
information_valuepredicts citation. Ascoreper fact ("how decision-relevant to this action space") is a third arm the existing eval can score against the same MRR / top-k baselines. - Surface-tier routing — deterministic tier vs LLM tier vs bigger model per decision (the LangChain harness pattern); cheap enough per turn.
- A semantic guard behind the
Guardprotocol — anoul"does this order contradict the active directive", advisory only, never a denial (StateGuard checks arithmetic; this would check intent).
5. First experiment
Slots 1 and 2 in camayoc: both already record method and threshold, so a Jev arm is a scorer selection plus a benchmark against the lexical arm on the same inputs. Report agreement, confidence distribution, and the NO COVERAGE rate.
Built 2026-09-21 (slot 2, question mapping): scripts/jev.py is the only Jev client (bearer
key from TYPESAFE_API_KEY; no key -> JevUnavailable, never a silent
fallback). competency.py --method jev poses ONE choice per asked question
with the 91 suite questions plus a reserved none-of-these option; the verdict
carries method: jev-latest-choice-v1, semantic: true, the instructions, the
model, the confidence, the none probability, and abstained, which forces
Empty. Tests: tests/test_jev.py (fake transport, no network). Slot 1
(settled-decision collision, noul) is next.
Run it:
export TYPESAFE_API_KEY=... # from your secret store
python3 scripts/competency.py --method jev "what did we decide about the build-host reboot?"
python3 scripts/competency.py --method lexical "what did we decide about the build-host reboot?"
6. Blockers before any of it
Jev API access (waitlist)Account exists (2026-09-20). The key lives in Infisical asTYPESAFE_API_KEY(aegis-6016ma rule); scripts read it from the environment only.EgressMeasured 2026-09-21:POST api.typesafe.ai/v1/systemoneanswers 403 (reachable, unauthenticated) in 0.24 s from both crew hosts. The database host is still unmeasured.- Quipu "deliberately contains no LLM" — Jev calls live in camayoc scripts (the ingress layer), never in the store.
7. Open questions
- Should a Jev verdict be its own episode kind (
camayoc:TypedVerdict) or a property set on the existing collision/coverage verdict records? - Confidence floors: measured per slot, not one global number.
- Does a
scoreover the promotion queue need a rubric per plane?
Tracked: aegis-sfg5vb.
8. Across the stack (added 2026-09-21, Stiwi: "make progress on all of these")
Jev fits one job: a bounded decision with a state to judge, made often, where a calibrated probability beats a rationale. The stack has that shape in more places than camayoc. Each row is a bead under the epic named at the bottom.
| Where | Decision | Shape | Fit | Existing bead |
|---|---|---|---|---|
| camayoc | settled-decision collision | noul per standing decision | strong | camayoc-7lt (lexical today) |
| camayoc | promotion-queue triage | score on evidence quality | good | aegis-f8efkn (quipu side) |
| camayoc | entity-mention disambiguation | choice + neither | cautious | camayoc-0c8 (abstains today) |
| bobbin | retrieval floor: does this chunk answer the query | noul per chunk | strong | aegis-xd2nko (insufficient context) |
| yupana | grounded-predicate evaluator for edit policies | noul per predicate | strong | aegis-vwvjwl |
| shantytown | board hygiene: route by domain, duplicate?, escalation severity | choice / noul / score | strong (volume) | aegis-cvd2xu context |
| homelab ops | alert triage: severity + owner; governor stop deliberate-or-fault | score + choice | good | — |
| NeuralAmplifier | grounding-fact rank, tier routing, semantic guard | score / choice / noul | good | na-htm |
| evals | rubric judge for NA evals and quipu conformance | score | good | — |
| resume (hammond) | recruiter inbound disposition | choice + nouls | immediate | job-patrol skill |
Never: inside quipu (no model in the store), plane routing by sourceKind,
promotion itself. Jev ranks and flags; humans and deterministic code decide.
8.1 Fleet tooling: one way for any st agent to ask Jev
Every row above needs the same three calls. They must not each grow a client.
scripts/jev.pystays the single client (camayoc owns ingress and the ingress discipline that every Jev answer isinferred).jev-mcp: a stdio MCP server in camayoc exposingjev_noul,jev_choice,jev_score,jev_dry_run(the request without sending) andmap_question(an asked question -> competency id, probability, confidence, abstention, stored-query name; floor 0.75, never a guess below it). Delivered through the camayoc plugin (Stiwi 2026-09-21): declared in.claude-plugin/plugin.jsonmcpServers, so/plugin install camayoc@camayocis the whole registration; shantytown only projects the manifest at launch (aegis-b08zsc). The skill's query first move becomes map, then run the stored query, then file an abstained question as a candidate competency question./camayoc:bootstrapoffers the Jev step like it offers bobbin and yupana: asks whereTYPESAFE_API_KEYlives, proves a dry run and one livenoul, reports unreachable/unauthorized honestly. Never a key in a card or a template.- CLI parity:
python3 scripts/jev.py {noul,choice,score}for shells and cron. Both surfaces logusage.input_tokensand the model string. - Guardrails baked in, not documented:
choicealways accepts anone_textand the MCP tool defaults it on; every result carries the request; a missing key is an error the agent can read, not a fallback. - Cost line: at ~$0.0004 per decision, 10k decisions/day is ~$4. The MCP server counts calls per agent so the governor can see it.
8.2 The benchmark that must exist before any arm is trusted
A labelled set per slot (≈30 items with the human-correct answer), then per arm: agreement with the label, confidence distribution on agreements vs disagreements, abstain rate. Thresholds come from that, never from a default. The competency slot's first three live calls (2026-09-21) already produced one "confident but arguable" pick; that is the item type the set needs most.
8.3 Question-mapping results, 2026-09-21 (35 labelled items, hammond as ontology owner)
| arm | agreement | conf when right | conf when wrong | abstain | input tokens |
|---|---|---|---|---|---|
| lexical | 54% | — | — | 0% | 0 |
| jev (flat, 91 options) | 94% | 0.96 | 0.64 | 17% | 163,920 |
| jev-hier (file then question, +examples) | 74% | 0.84 | 0.45 | 31% | 76,426 |
Rulings from this: flat stays the default; a confidence floor of 0.75 turns below-floor verdicts into "a human reads it" rather than a filed gap (the 0.96/0.64 split is what makes the floor meaningful); the two-stage variant halves the tokens but loses 20 points, almost all at the file stage, so it is kept as a measured option and not used. Cost of the default: ~4.7k input tokens (< $0.01) per asked question at list price.
8.4 Vocabulary (Stiwi 2026-09-21)
Mapping, not coverage. Jev maps an asked question onto the competency
suite (or abstains). Coverage is the deterministic question — does a
competency question have a stored query that answers it — and stays with
query_coverage.py. The verdict's coverage: Empty|Partial|Full field keeps
its pre-Jev name (camayoc-b6h) because other tools read it; read it as "how
well the asked question maps".
The camayoc / yupana thesis boundary
Implementation status (2026-08-17): ✅ Proposed and written down on both sides. Agreed by: pending keeper sign-off. The mirror of this section is in
yupana/docs/design/paper.md§2.1; if the two ever disagree, neither paper should draft until they are reconciled.
Why this exists before either paper drafts
camayoc-105 names the risk precisely: both papers instantiate one principle —
a check that cannot fail is not a check, and absence of a finding is not a
finding of absence — and if both claim it, reviewers see one idea published
twice and both papers weaken.
Retrofitting a boundary after two drafts exist is much harder than agreeing one now, so this is written before drafting rather than after.
The split
The shared principle is cited by both and claimed as a contribution by neither. It is older than either system. What each paper contributes is a layer at which the principle is enforced, and the mechanisms that enforcement required.
| camayoc | yupana | |
|---|---|---|
| Layer | Admission — what may enter the graph at all | Action — what a guard may report at the edit boundary |
| Question | Should this fact be stored? | Should this action proceed, and what may silence mean? |
| Time | Write time | Action time |
| Mechanism | Refusal at ingress (SHACL) | Typed verdicts from a hot projection |
| Owns | Provenance-refusing ingress; falsifier-gated verification; the closed source-tier vocabulary; the incident-night corpus | The non-answer taxonomy: vacuous ≠ pass, unevaluated ≠ skipped, served_from_cache ≠ fail_open, unknown ≠ unsatisfied, empty-board refusal, STALE-with-AGE |
| Evidence | 47 competency questions, the 2026-08-06/07 incident corpus, refusal tests | Guard latency, degradation under load, NeuralAmplifier as a second domain |
Put in one line each:
- camayoc: knowledge must earn its way in, and a verification that cannot fail has not earned anything.
- yupana: a guard that returns nothing must say which kind of nothing.
Each paper cites the other for the layer it does not own.
Two collisions the bead did not anticipate
Reading both plan docs against each other turned up two places where the split above is not self-executing. Both must be settled before drafting.
Collision 1: the installation-time gate proof
yupana/docs/design/paper.md §4 closes with:
the installer proving each refusal gate with deliberately invalid probes that each omit exactly one required property, so the arms discriminate
That is camayoc's mechanism, described in yupana's paper. It lives here:
scripts/gate_probe.sh (248 lines) and tests/test_gate_probe.py (302 lines),
and establishing that the arms genuinely discriminate is an open camayoc bead
(camayoc-104).
Proposed resolution: camayoc owns it, yupana cites it. The reasoning is that the probes prove admission gates — they establish that a refusal shape is enforced rather than merely declared, which is the admission thesis applied to camayoc's own installer. Yupana's use of the same idea is a one-line citation, not a contribution, and the sentence above should become a citation in yupana's draft.
This is a nice piece of self-application and worth a sentence in camayoc's write-up: the paper's own thesis, turned on the paper's own installer. Yupana calls the same discipline non-vacuity; the papers should use one name for it, and since the mechanism sits here, camayoc's term should follow yupana's existing published usage rather than mint a second one.
Collision 2: Empty | Partial | Full coverage reporting
Both systems report coverage as a graded verdict rather than a bare count:
- camayoc:
scripts/competency.pyreturnsEmpty | Partial | Fullfor ontology coverage, with aNO COVERAGEoutcome. - yupana: dataset coverage reported as
empty | none | partial | full.
Proposed resolution: no exclusivity is needed, because neither paper claims the shape as a contribution. They apply it to different objects — an ontology's reach versus a dataset's population — and each should cite the other as an independent instance. Two systems converging on the same reporting shape from different directions is evidence for the principle, and is worth one sentence in each paper saying so. What must not happen is either paper presenting the shape as novel.
What this costs camayoc, stated plainly
The boundary is not free, and the paper should not pretend otherwise.
Handing the non-answer taxonomy to yupana means camayoc gives up its most immediately legible contribution — ten crisp distinctions, each forced by an incident, is very good paper material. What camayoc keeps is narrower and harder to make vivid: a write-time refusal, a mandatory falsifier, and a corpus.
Two things make that a good trade. First, camayoc's typed non-answers are
one instance (NO COVERAGE, on the ontology's own coverage — see
implemented-set.md aspect 8), while yupana's are a systematic taxonomy with
per-distinction incident provenance. The stronger claim belongs where the
stronger evidence is. Second, camayoc's retained territory contains the thing
neither other paper can claim: the incident-night corpus, eleven measured
instances of unfalsifiable verification on a live crew. Most papers about
verification integrity have no field data at all.
Relationship to the third leg
Both papers sit in a three-paper program and should say so identically:
- quipu compiles SARC into the store — submitted to arXiv (no public identifier yet).
- yupana compiles it into the action.
- camayoc governs admission — what may enter at all.
The nearest external anchor for camayoc is SARC-DQ, which quipu's related-work section describes as gating evidence quality; evidence quality is camayoc's territory rather than yupana's, and that is the cleanest one-sentence statement of why the admission leg is a separate paper.
Design: Camayoc paper plan — how knowledge earns its way into the graph
Implementation status (2026-08-22, second revision): 🟡 Planned. Nothing drafted. The evaluation is no longer thin: 16 of the verification slice's 19 competency questions run as stored queries (
just query-coverage), aftercamayoc-89elanded the five single-edge mints (Q3/Q4/Q5/Q7/Q12) andcamayoc-e29the §D cost-accounting vocabulary (Q16–21), both on 2026-08-22. The figure's history matters for §5: it was reported as 5/13 until earlier the same day, when the coverage tool was found to be omitting §D from its denominator (5/19 once counted). The three gaps that remain — Q6, Q8, Q9 — are all the Principal/liveness modelling, which is a design decision, not an edge. A newer slice, golden paths, stands at 12/16 stored. Readimplemented-set.mdbefore drafting a single sentence; it is the gate on what this paper may claim.
Status
- Date: 2026-08-22 (rev 2; rev 1 2026-08-17)
- Status: Planning. Boundary agreed; implemented set measured; evaluation gap narrowed to the liveness family (Q6/Q8/Q9) and quantified.
- Related:
implemented-set.md(what may be claimed),thesis-boundary.md(the camayoc/yupana split),incident-corpus.md(the empirical core),ingress.md(the mechanism),../vision.md(the TrustGraph foil), the provisional filing (kept privately) (D — claims more than is built).
1. Intent and thesis
Thesis, from the README: how knowledge earns its way into the graph, so that the store never has to trust an extractor.
Sharpened for a paper:
A knowledge store that accepts machine-written facts has a trust problem it cannot solve internally, because by the time a fact is in the store the question "why should I believe this?" has already been answered — badly. The answer must be a refusal at admission, not a confidence score at read time. And the refusal must itself be falsifiable, or it is decoration.
Two halves, and the second is what makes it a paper rather than a design note.
Anyone can add a mandatory provenance tag. The contribution is applying the
same standard to the checks themselves: a Verification is refused at ingress
unless it names the result that would have proved it wrong.
That constraint sounds pedantic until you have field data. We have field data (§4), and it says that on one night on a live crew, eleven separate checks could not have failed and were reported as verification by careful people.
2. Positioning
The three-paper program
Worded identically here and in yupana's plan:
- quipu compiles SARC into the store — submitted to arXiv (no public identifier yet).
- yupana compiles it into the action.
- camayoc governs admission — what may enter at all.
The nearest external anchor is SARC-DQ, which quipu's related-work section describes as gating evidence quality. Evidence quality is precisely camayoc's territory rather than yupana's, and that is the one-sentence justification for the admission leg being its own paper.
The named foil: TrustGraph
Already stated in ../vision.md and it should open the paper,
because it is concrete and it is a real system rather than a straw one:
The temptation is to bolt an extraction pipeline onto the store. TrustGraph and its peers do exactly that, and it works — until you ask "why should I believe this fact?" and the answer is "a model wrote it during ingestion."
The paper's structural claim follows: the extraction layer must live outside the store, with its own discipline, its own tests, and its own refusals. Be fair to the foil — the integrated approach has real ergonomic advantages, and the paper is stronger for saying so before arguing the trade is wrong for agent-written knowledge specifically.
The boundary with yupana
Settled in thesis-boundary.md and mirrored in yupana's
plan §2.1. Short version: camayoc owns admission, yupana owns the guard's
non-answers at the action boundary, neither claims the shared principle, each
cites the other. Two collisions were found and resolved there — the installer
gate proof (camayoc's, yupana cites) and Empty|Partial|Full coverage
reporting (convergent, both keep, neither claims).
3. Contributions
- C1 — Provenance-refusing ingress with a closed, per-class-narrowed
vocabulary.
aegis:sourceKindis mandatory and constrained toobserved | declared | inferred; a write carrying an unrecognised value fails now, not at read time. Built; refusal tested (tests/test_knot_provenance.py). - C2 — Falsifier-gated verification.
aegis:falsifier,sh:minCount 1, on everyVerification. A verification that does not name what would have disproved it is refused at ingress rather than stored and believed. Built. This is the paper's sharpest single mechanism and the one with no obvious precedent in knowledge-graph ingress. - C3 — The incident corpus. Eleven measured instances of unfalsifiable
verification and a four-mechanism/one-missing-predicate liveness family, from
one night on a live crew, taxonomized into seven recurring forms. See §4 and
incident-corpus.md. Most papers about verification integrity have no field data at all. - C4 — Installation-time gate proof. Each refusal gate is proven by a probe
that omits exactly one required property, so the arms discriminate — the
paper's own thesis applied to the paper's own installer. Built
(
scripts/gate_probe.sh,tests/test_gate_probe.py), and independence is now established (camayoc-104): each probe provably omits exactly one required property, and a store enforcing one shape at a time proves only its own arm. Claimable as stated. - C5 — Liveness by deliberate absence, as a design position. Judgments that decay are never stored; liveness is a read-time join. Claim the position and the vocabulary. Do not claim the mechanism — see §6.
4. The empirical core
Full write-up in incident-corpus.md; source is
competency/verification-and-liveness.md, recorded contemporaneously on
2026-08-06/07.
Section A — verification integrity. Seven forms, each measured at least once:
- a guard documented as belt-and-braces that was the only mechanism
- a byte-identity test that passed against a read-write open
- a schema check comparing
(type, name)only, soADD COLUMNwas invisible - a binding condition whose two branches were the same function
- a
grep -cmatching the description of the bug in the test payload - a
grepagainst an empty capture whose0read as "absent" - a "corroboration" equally consistent with both competing hypotheses
The unifying shape — and the sentence the paper should be built around — is that none of these was a wrong answer. Each was a check performed against something adjacent to the claim, and reported as verification.
Section B — held-work liveness. Four simultaneous P1s, one root cause:
every routing mechanism recorded an owner without checking the owner was alive.
Bead assignment, alert keeper labels, BLOCKED status, in_progress status —
four mechanisms, one missing predicate, identical worst-case consequence: a
thing correctly detected and correctly recorded is indistinguishable, from every
dashboard, from a thing that was never detected at all. The sharpest instance
is recursive and should be quoted verbatim: the bead documenting invisible
in-progress work, sitting in-progress, invisible, on a stopped agent.
Section C — armed ≠ merged ≠ correct. A guard correct in git and absent from every execution path for 24h; a rules file six weeks stale reaching agents while the current one sat in the repo.
The two attacks this section must survive
Named in camayoc-101 and both are fair:
- Selection bias. Were these found by looking for them? What is the denominator? A corpus assembled by searching for instances of a pattern proves the pattern is findable, not that it is common.
- Count vs rate. "Eleven instances" is unanchored. Eleven out of what, over what period, on how many agents?
incident-corpus.md answers both explicitly, including
where the honest answer is "we cannot compute this from the record we kept".
Do not paper over that. A corpus with a stated denominator gap is credible;
a corpus with a silent one is not, and the paper's own thesis is that a claim
must name what would falsify it.
5. Evaluation — and its honest state
Methodology: competency questions, which are the standard for ontology work.
The suite is 91 questions across six slices, machine-readable and watermarked
(sha256:65bc4c5ad3d84752, just competency-list). The suite sets its own bar:
every question must eventually run as a named stored query.
Coverage of the verification-and-liveness slice: 16 of 19 (Partial).
Reported per question by scripts/query_coverage.py (just query-coverage),
and the figure is pinned by a test so it cannot drift silently.
The figure's history is itself evaluation material. camayoc-102 measured
5/13; the 2026-08-22 audit found the coverage tool omitting the slice's own
§D (cost accounting, Q16–21) from its denominator — 5/19 once counted, an
uncounted question being a gap unreported. Later the same day camayoc-89e
landed the five single-edge mints (Q3/Q4/Q5/Q7/Q12) and camayoc-e29 the §D
vocabulary (Q16–21), each term owed to its question: 16/19.
A suite-wide denominator now exists, and this section does not yet use it (2026-08-25). The same defect recurred one level up:
SLICESin the coverage tool listed two of the suite's six files, so four slices and 56 questions carried no verdict — and four already-stored metrics queries counted toward nothing, meaning the tool understated real coverage while appearing complete. All six slices now have tables, a test asserts set equality between them andcompetency/*.mdso an uncounted slice cannot land again, andjust query-coverageprints a suite total: 40 of 91 stored, 22 expressible and unwritten, 29 competency gaps (32/91 when the denominator first existed;camayoc-rkbtook workflow-and-archive from 0/14 to 8/14 later the same day). The per-slice figures above are unchanged and remain correct.What number this section should lead with is an authorial decision and is deliberately left open. 16/19 is the strongest true statement available and 40/91 is the most complete one; they are not in conflict, and choosing between them — or reporting both, with the three states kept apart, since an unwritten query and a competency gap are different claims about the ontology — is a framing call for whoever drafts §5, not a figure to be swapped in by a sweep.
| Questions | State | |
|---|---|---|
| Stored and executing | Q1–Q5, Q7, Q10–Q13, Q16–Q21 | falsifier retrieval and staleness, adversarial proof, variable dependence, closed-dependency blocks, blockers by evidence kind, execution paths and drift, cost accounting |
| Competency gaps | Q6, Q8, Q9 | all the Principal/liveness modelling, each reported with the terms it would need |
The remaining gaps are one family, and it is the pointed one:
- Q6, Q8 and Q9 (liveness) block the family §4 leads with. There is no
Principaland no observed stop/heartbeat record. This is correctly not a stored fact — ingress rule 5 forbids storing judgments that decay — but a read-time join still needs something to join to, and nothing is modelled (aegis:Session, minted for §D, carries a principal but says nothing about running). The paper must say this plainly: the four-beads-one-cause corpus motivates a capability the ontology cannot yet express. - Q4 is now a closed loop worth a paragraph.
camayoc-104established proven-able-to-fail for the gate probes;aegis:adversariallyProvenBynow records the same property about any check, and the proof node is itself a falsifier-gatedVerification. The system practises the discipline and can describe it — and the date each half landed is in the history. - Q2 has a subtlety worth a footnote. A
Verificationwithout a falsifier cannot be written through a gated ingress, so Q2's population is precisely the store's pre-gate legacy, and against a store that was always gated it correctly returns nothing. The fixture seeds pre-gate rows so the query is proven able to find them. - §D's acceptance is self-application again. Q16 and Q18 run against a fixture whose totals are stated independently (what the harness files print) and reproduced by the queries — a usage reader checkable only against itself being the section-A defect in an accountant's hat. The UNKNOWN-never-zero rule is tested from both sides: unmeasured items and decisions return no row, and Q19 returns the unmeasured session itself.
Every stored query executes against a fixture with both arms — a seeded positive finding and a control negative — per the suite's own acceptance criterion: a query that cannot return findings has the same defect as the checks in section A, one level up. That self-application is the sentence to put in §7.
What this means for the claim. The liveness corpus in §4 still motivates a capability rather than demonstrating one, and the paper must say so — but the shortfall is now three questions, one named family. The verification-integrity and cost-accounting halves of the slice are demonstrable, not merely motivated.
6. Scope boundaries (honest)
From implemented-set.md, which is authoritative:
-
Quarantined inference and governed promotion are BUILT; one quipu-side mechanism is not. This bullet said the opposite until 2026-09-05, and was contradicting the very document it cites as authoritative — see the note below, which is kept because how it went stale is the reusable part. Re-measured 2026-09-05:
- Quarantined inference — built on both sides. Camayoc routes inferred
facts to a distinct named graph labelled low in the trust lattice
(
scripts/planes.py, 13 tests) since 2026-08-18; quipu materialises into companion inferred graphs with a write guard (src/store/inferred.rs, 17,732 bytes, 13 functions) since 2026-08-27,df36f72. - Governed promotion — camayoc's half is built (
scripts/promote_plane.py, 15 tests, authority-gated and self-promotion-refused, move half since 2026-08-24). Quipu's promotion mechanism is not:grep -ci promote src/store/inferred.rs->0, and quipu's ownentailment-regime.mdstill lists "REMAINING under quipu-0b6: the promotion mechanism (§3 — authority-gated…)".
So future work names one blocker, not two, and it is quipu-side rather than camayoc-side.
How this went stale, recorded because the mechanism is the point. The claim was true when written (2026-08-22).
implemented-set.mdre-measured on 2026-08-25 and moved both aspects to Built; quipu's half landed 2026-08-27. This bullet moved on neither date, and for fourteen days asserted "NOT built" while naming as authoritative a document that said "Built" — a contradiction inside one repo, which no cross-repo watch would have caught.tests/test_paper_claims.pynow fails on exactly that shape. It also carried a falsifiable claim, "Zero non-doc references exist", which was false by two scripts and 28 tests. - Quarantined inference — built on both sides. Camayoc routes inferred
facts to a distinct named graph labelled low in the trust lattice
(
-
Tier-honest serving is yupana's. Camayoc defines and mandates the tag; yupana serves under it. Claim the vocabulary, not the serving.
-
Typed non-answers: one instance, not a taxonomy.
NO COVERAGEon the ontology's own coverage. The systematic taxonomy is yupana's. -
Liveness: a position and a vocabulary, not a mechanism. The terms exist (
ontology/core.ttl:93, :120) and the refusal to synthesise a write-time liveness predicate is explicit and deliberate (shapes/core.shapes.ttl:140). The read-time queries do not exist.
7. Paper outline
- Introduction — the TrustGraph question: why should I believe this fact?
- Background — SARC and SARC-DQ; the three-paper program; what quipu and yupana already claim.
- The admission problem — why a store cannot solve trust internally.
- Mechanism — closed provenance vocabulary, write-time refusal, the
falsifier constraint on
Verification. - Proving the gates — installation-time probes, one omitted property each, arms that discriminate. Self-application of the thesis.
- Field evidence — the incident corpus, with denominator and rate stated.
- Evaluation — competency questions, per-question coverage, gaps as findings.
- Limitations — §6, in full and unhedged.
- Related work — TrustGraph and extraction-in-store platforms; SARC-DQ;
provenance vocabularies (PROV-O — say plainly why
sourceKindis narrower and why narrowness is the point); SHACL validation as a gate rather than a report; ontology competency questions (Grüninger & Fox) as evaluation methodology. - Conclusion.
8. Build order
Agree the yupana boundary— done,thesis-boundary.md.Produce the implemented set— done,implemented-set.md.Write up the incident corpus with denominator and rate— done,incident-corpus.md.Prove the gate probes adversarially— done (camayoc-104); C4 is claimable.Close the competency-query gap for the verification slice— done (camayoc-102), reaching 5/13 at the time and converting the other 8 into named ontology gaps. (Recounted as 5/19 on 2026-08-22 when the slice's §D entered the denominator — six more named gaps, no lost queries. The five single-edge mints landed ascamayoc-89eand the §D cost vocabulary ascamayoc-e29, both 2026-08-22: 16/19.)- Decide whether to close the liveness gap before drafting. Q6/Q8/Q9 (the Principal/liveness modelling) is all that remains, and it is the one that changes what §4 can claim; it can also ship as future work with the blocker named. This is now the only open build decision, and it is ian's.
- Draft §§1–5 from the mechanism docs; they are stable.
- Draft §7 last, against whatever coverage step 6 settles on.
9. What this paper is not
- Not a retrieval paper. That is bobbin's, and its own measurement problems are documented there.
- Not a store paper. Quipu's is submitted and its evaluation is spent.
- Not a guard paper. Yupana owns the action boundary and the non-answer taxonomy.
- Not a claim that quipu-side promotion of quarantined facts is built — it is not (see §6). Quarantined inference itself is built and claimable; this line said otherwise until 2026-09-05.
The implemented set — what the paper may claim
Implementation status (2026-08-25): ✅ Current — re-measured. Every row below was measured against this repo on this date, with the grep or file that produced it. Re-run before drafting; do not carry a row forward on trust. This re-measure changed one verdict: aspect 3 is no longer partial — the retraction half of the move landed with the workflow slice on 2026-08-24 (
camayoc-913) and the row had been carried forward on trust against the row two lines above it, which already said so. The 2026-08-22 re-measure had changed two: aspects 2 and 3 were design-only on 2026-08-17 and are no longer (camayoc-s0handcamayoc-mipclosed 2026-08-18 withscripts/planes.pyandscripts/promote_plane.py).
camayoc-100 exists because a provisional patent and a systems paper have
different standards of proof. A patent may claim constructively reduced
embodiments. A systems paper may claim only what is built and measured.
Provisional D discloses eight aspects. This document states, per aspect, what
is built, what is partial, and what is design-only — so drafting starts from
the implemented set rather than from D's table of contents.
Getting this wrong is the fastest route to an unrecoverable review: a reviewer who finds one unbuilt claim stops believing the built ones.
Verdict table
| # | D aspect | State | Evidence |
|---|---|---|---|
| 1 | Provenance-refusing ingress | ✅ Built | shapes/core.shapes.ttl makes aegis:sourceKind mandatory; shapes/code-entities.ttl:50 constrains it to the closed observed|declared|inferred vocabulary. Refusal proven, not asserted — tests/test_knot_provenance.py (216 lines). |
| 2 | Quarantined inference | ✅ Built (camayoc-s0h, 2026-08-18) | scripts/planes.py registers the planes as quipu named graphs and labels them in the trust lattice — both or neither, and plane_for refuses an unknown sourceKind rather than defaulting to ROOT. Refusals pinned by tests/test_planes.py (13 tests): inferred never shares a plane with observed and always ranks strictly below it. Runtime caveat to state: it requires quipu's POST /graph/create + /graph/label routes (added upstream for the bead); against an older quipu it reports UNAVAILABLE and bootstrap fails rather than writing to ROOT. See docs/design/ingress.md §2.1. |
| 2b | Windowed operational ingress + the move rule | ✅ Built (2026-08-24) | Routing is 2-D: plane_for(source_kind, data_kind) refuses operational toward the static planes; scripts/windows.py creates-and-labels month windows (operational/fresh/soleRecord) for deep freeze; the same change landed the promotion move rule, which is now carried by aspect 3's row rather than duplicated here. Pinned by tests/test_planes_2d.py (16 tests). See docs/design/workflow-and-archive.md. |
| 3 | Governed promotion | ✅ Built (camayoc-mip 2026-08-18; the move half, camayoc-913, 2026-08-24) | scripts/promote_plane.py: authority-gated (fails closed on a missing config/plane-authority.json), refuses self-promotion, refuses non-upward moves, refuses promoting what is not in the source plane; records camayoc:planePromotion with full provenance. 15 tests in tests/test_promote_plane.py, almost all refusals, plus the happy-path control. It is now a graph move, not a copy-up: promote() returns (assert, close) and main commits assert → POST /episode/retract on --source-episode (on_orphan: preserve) → record. The retraction is a bitemporal interval close, never a delete, which is what answered the governance question that held this row open: valid-time replay still shows the fact where and when it lived (competency/workflow-and-archive.md Q13). Commit order is load-bearing and tested both ways — a failed close leaves the fact readable in two planes at different trust (exit 4, stated out loud with the exact retry body), where close-then-failed-assert would lose it outright. Absent --source-episode the record carries camayoc:sourceLeftOpen true rather than implying a close that never happened. Pinned by tests/test_planes_2d.py MoveRuleTests + MoveRuleCommitOrderTests (8 tests). |
| 4 | Falsifier-gated verification | ✅ Built | shapes/core.shapes.ttl:89-95 — aegis:falsifier, sh:minCount 1 on Verification, with the refusal message. Probed by scripts/gate_probe.sh; the probe's own discrimination is camayoc-104. |
| 5 | Liveness by deliberate absence | 🟡 Partial — and the partiality is the point | The vocabulary exists (ontology/core.ttl:34, :120 — aegis:ExecutionPath, aegis:ownedBy; the 2026-08-17 row cited two term names that do not appear in the file), and the deliberate absence is explicit at shapes/core.shapes.ttl:130: liveness "belongs with hank/quipu, not a made-up write-time liveness rule". What does not exist is the read-time side: the liveness-join questions (verification-and-liveness #6, #8, #9) have no stored queries — #10 gained one (camayoc_blockers_by_evidence_kind) with camayoc-102, and #7 gained camayoc_blocked_on_closed_dependency with camayoc-89e (2026-08-22; not a liveness join — it needs only blockedOn and the already-modelled closedAt). See §2. |
| 6 | Installation-time gate proof | ✅ Built, and now independence-proven | scripts/gate_probe.sh with tests/test_gate_probe.py holding the probes to being able to fail. camayoc-104 closed the remaining gap: each probe is shown to omit exactly one required property (parsed from the shapes, not hardcoded), and a store enforcing one shape at a time proves only its own arm. Claimable as stated. |
| 7 | Tier-honest serving | 🟡 Partial | aegis:tier is carried on policies (shapes/policies/edit-grounding.ttl:49, :71, :78) and the tier vocabulary is in ontology/core.ttl:54. But camayoc does not serve facts — yupana does. What camayoc owns is the tag's definition and its mandatory-ness, not the serving. Claim the vocabulary; do not claim the serving. |
| 8 | Typed non-answers | 🟡 Partial, one instance | Fully realised in exactly one place: scripts/competency.py returns Empty | Partial | Full with a NO COVERAGE verdict, method/threshold/watermark carried, 18 tests. That is a real instance of the discipline. It is one instance, on the ontology's own coverage, not a general non-answer taxonomy across camayoc's surfaces. |
Summary: five built (1, 2, 3, 4, 6), three partial (5, 7, 8), none design-only. (On 2026-08-17 this read three/three/two; aspects 2 and 3 landed on 2026-08-18, and aspect 3's remaining half — the retraction — on 2026-08-24, which is what moved the count to five/three.)
1. What this means for the paper
Claim without qualification (aspects 1, 4, 6). Provenance-refusing ingress, falsifier-gated verification, and installation-time gate proof are built, tested, and — critically — their refusals are tested, not just their acceptances. These three are the paper's spine and they are strong: the falsifier shape in particular is a genuinely unusual constraint to enforce at write time, and it is enforced.
Claim narrowly, with the boundary stated (aspects 5, 7, 8).
- Liveness by deliberate absence should be claimed as a design position
with a vocabulary, not as a mechanism. The position is defensible and
interesting — refusing to synthesise a write-time liveness predicate, because
liveness is a read-time join against a store that actually knows — and
shapes/core.shapes.ttl:140shows the refusal was deliberate rather than an omission. But the paper must not imply the liveness questions are answerable today. They are not; see §2. - Tier-honest serving is yupana's, not camayoc's. Camayoc defines and mandates
the tag; yupana serves under it. Claim the former. This is also the cleanest
illustration of the boundary in
docs/design/paper.md§2. - Typed non-answers should be claimed as one worked instance
(
NO COVERAGEon ontology coverage) that demonstrates the discipline, with the general taxonomy attributed to yupana's guard, which has ten distinctions and the incidents that forced each.
Now claimable, carefully (aspects 2, 3). On 2026-08-17 this section said
do not claim, at all — quarantined inference and governed promotion were D's
headline pairing and were not built, both blocked on quipu routes. On
2026-08-18 both landed (camayoc-s0h, camayoc-mip; the quipu routes were
added upstream on the same branch). The claims the paper may now make, with
their edges stated:
- Quarantined inference is claimable as built: inferred facts route to a
named graph labelled low in the trust lattice, registration and labelling
are atomic, and an unroutable write fails rather than landing in ROOT. The
caveat to state is deployment-shaped, not mechanism-shaped: it requires a
quipu with the
/graph/create+/graph/labelroutes, and the failure against an older store is a refusal, which is the honest direction.
Cross-repo blocker, as of 2026-09-05 (muldoon). These two rows depend on quipu, and a row whose truth lives in a sibling repo goes stale without anyone here touching anything — this banner's trigger is drafting, which never fires on a dependency. So the blocker carries its OWN date, separate from the re-measure date above:
quipu-side mechanism as of 2026-09-05 evidence quarantined materialisation BUILT (landed 2026-08-27, df36f72)src/store/inferred.rs, 17,732 bytes, 13 functions; companion inferred graphs + write guardgoverned promotion NOT built grep -ci promote src/store/inferred.rs->0; quipu'sentailment-regime.mdstill lists it under REMAININGThe first half cleared two days after the 2026-08-25 re-measure above, so that re-measure was correct when taken and wrong eleven days later. That is the failure mode this table exists to make visible, not a lapse.
- Governed promotion is claimable as built, both halves: the governance
half — authority-gated, self-promotion-refused, upward-only,
provenance-recorded, with fifteen tests that are almost all refusals — and,
since 2026-08-24, the move half.
camayoc-913was open because retracting fromcrew:inferredlooked like erasing the low-trust past; bitemporality answers it, and the close is an interval close on the source episode rather than a delete, so as-of replay still finds the fact in the plane it was written to. The edge the paper must still state is the honest one the mechanism states itself: when the caller cannot name the source episode, the promotion proceeds withcamayoc:sourceLeftOpen trueon the record — a copy-up that says so, not a move that pretends. Saying "promotion" while meaning "copy-up with an audit record" without that flag would be exactly the adjacency defect the corpus documents; the flag is what keeps the claim clean.
2. The gap the audit surfaced: liveness is unqueryable
Aspect 5 turned up something the bead did not anticipate and that
camayoc-102 needs.
As this audit was written the competency suite was 47 questions across three
files (sha256:7a0cc4abad386885) and the repository contained four stored
queries, all in queries/, all four serving the metrics slice. The figures
in this paragraph and the block quotes under it are kept in past tense on
purpose: they are the record of what was measured when, and the update chain
below is what supersedes them. The current figures are just competency-list
and just query-coverage, never this page.
$ ls queries/
camayoc_metric_reachability_candidates.json
camayoc_metric_retrieval_method.json
camayoc_metrics_for_subject.json
camayoc_unvalidated_metric_claims.json
So the verification-and-liveness slice had zero stored queries — including
the four-beads-one-cause family (#6-#10) that camayoc-101 proposes as the
paper's empirical core, and the falsifier questions (#1-#5) that motivate the
aspect the paper is strongest on.
That was a coverage figure of 4/47 ≈ 9% when this audit was written.
Update after
camayoc-102: the verification-and-liveness slice now has 5 of its 13 questions stored and executing (just query-coverage), and working it produced a better finding than the count. Only five of the thirteen can be expressed at all with today's vocabulary; the remaining eight name terms the ontology does not carry, so they are competency gaps rather than unwritten queries. Aspect 5's read-time gap above is the largest of them: Q6 and Q8 need aPrincipaland an observed liveness record to join against, and neither is modelled.Update 2026-08-22: the suite is now five files (golden paths and document-structure/chunks joined it) and
queries/holds 21 stored queries.just query-coverageis the living figure and reports two slices: verification-and-liveness 5/19 — the denominator grew from 13 because the slice's own §D (cost accounting, Q16–21) had been left out of the coverage table, and an uncounted question is a gap unreported — and golden-paths 12/16, whose four gaps are all blocked outside this repo (quipupath cone; L5 verdict signing; yupana's guard). The eight original verification gaps are unchanged: nothing minted since covers them, and the five that are single-edge mints are tracked ascamayoc-89e; the §D six ascamayoc-e29.Update 2026-08-22, later the same day: both beads landed.
camayoc-89eminted the five single edges (verifiedAt/verifies,adversariallyProvenBy,dependsOnVariable,blockedOn,artifactDigest/sourceDigest) andcamayoc-e29the §D cost vocabulary (Session,UsageRecord,provider,tokensConsumed,inSession,attributedTo, withobservedAtandactorreused).queries/now holds 32 stored queries and verification-and-liveness stands at 16/19, pinned by test. The three that remain — Q6, Q8, Q9 — are all the Principal/liveness modelling:Sessiongives them a principal-bearing node but no observed stop/heartbeat record, so aspect 5's read-time gap above is now the WHOLE remaining gap of the slice.Update 2026-08-25 — the same defect, one level up, and a structural fix. The 2026-08-22 entry above congratulates itself on catching an undercounted denominator inside a slice. The tool was undercounting SLICES at the same time:
SLICESlisted two of the suite's six files, socrew-task-lifecycle(15),document-structure-and-chunks(14),metrics-and-requirements(13) andworkflow-and-archive(14) — 56 questions — carried no verdict at all. It also understated real coverage, which is the detail worth keeping: four stored queries shipped for the metrics slice on 2026-08-07 and counted toward nothing, so the tool reported less than the repo had while appearing complete.Restating the lesson is what failed, twice. The fix is a test, not a note:
tests/test_coverage_slices.pyasserts set equality between the coverage tables andcompetency/*.md, and between each table's question numbers and the questions the suite parser finds, so a slice or a question cannot land without a row. The report also now separates UNWRITTEN (the ontology carries the terms; nobody wrote the query) from GAP (the ontology cannot express it) — levels the tool's own docstring had always distinguished and its output had always collapsed.The honest suite-wide figure, first computable on this date: 32 of 91 stored across six slices, 29 expressible and unwritten, 30 competency gaps. Per slice: verification-and-liveness 16/19, golden-paths 12/16, metrics-and-requirements 4/13, and 0/15, 0/14, 0/14 for the three that had never been counted. Nothing regressed — 32 was always the number of stored queries; what changed is that there is now a denominator to put it over.
Update 2026-08-25, later the same day —
camayoc-rkb: 32 → 40 of 91. The workflow-and-archive slice went from 0/14 to 8/14. Q1–Q5, Q7, Q8 and Q13 are stored queries inqueries/camayoc_wf_*.json, executing againsttests/fixtures/workflow-archive.trig— a TriG dataset, not a graph, because this slice's questions are about where facts live as much as what they say. 36 tests intests/test_workflow_queries.py, each with a seeded positive and a control negative, and the controls are the interesting half: the same query composed against the wrong graphs returns a quietly short answer rather than an error, which is the failure the window scheme exists to prevent.One gap closed by a tool fix, none by minting. Q7 was recorded as blocked partly because
query_coverage.py's grounding self-check read only MINTED terms, so a stored query correctly reusing a quipu-owned aegis term (signature,VerifierRegistration,verifier) was reported UNGROUNDED — the tool penalising this repo's own reuse-before-minting rule. The fix is a reuse block inontology/core.ttlrecording each term's owner withrdfs:isDefinedByand no minting declaration, which the check now reads. Nothing was minted in the aegis namespace to close a gap.What stayed a gap, on purpose. Six of fourteen. Q9–Q12 and Q14 are one boundary: graph kinds, freeze state, thaw records and composed dataset labels are quipu meta-graph and store-table properties served by
GET /graphs, not triples SPARQL can reach — camayoc is the wrong place to answer them from and says so. Q6 is a different one: it asks whether a signature VERIFIES, and SPARQL can fetch a public key but cannot evaluate an ed25519 signature against it. Reporting the retrieval as coverage would let "a key is registered" pass for "the signature is good". The Principal / Session-stop / StateTransition family was not touched at all;ontology/core.ttl's decision not to pre-empt that design stands.Suite-wide: 40 of 91 stored across six slices, 22 expressible and unwritten, 29 competency gaps. Per slice: verification-and-liveness 16/19, golden-paths 12/16, workflow-and-archive 8/14, metrics-and-requirements 4/13, crew-task-lifecycle 0/15, document-structure-and-chunks 0/14.
The consequence for drafting order is narrowed and concrete: camayoc-101
writes up the incident night as a labelled corpus, and the natural reviewer
question is "could your system have answered these questions at the time?".
For section A (verification integrity) and §D (cost) the answer is now yes,
against fixtures with both arms; for section B (held-work liveness) it is
still no — a measured and attributable no, three questions blocked on the
Principal/liveness modelling. The paper states that the liveness corpus
motivates a capability rather than demonstrating one.
3. Method
Each row was produced by grepping for the mechanism outside docs/ and
competency/ — a term that appears only in design prose is design-only by
definition, which is exactly how aspects 2 and 3 were classified on 2026-08-17
— and how their reclassification was verified on 2026-08-22 (scripts/planes.py
and scripts/promote_plane.py exist outside docs/, with their tests). Gates
green at the time of writing: just test 46 passed (1 skipped), just check 0
issues across 17 files. After camayoc-102 and camayoc-104: 65 passed, 21
files. At the 2026-08-22 re-measure: 145 ran, 0 failed (32 skipped — the
server-backed arms skip without a quipu-server binary), 24 files. At the
2026-08-25 re-measure: 210 ran, 0 failed (52 skipped, same reason), 26 files.
The 2026-08-06/07 incident corpus
Implementation status (2026-08-25): 🟡 Labelled; the denominator is now instrumented, and unmeasured until a window runs through it. The instances and their taxonomy are recorded contemporaneously and are solid. The rate the paper needs is still not computable from the record of 2026-08-06/07 as it was kept, and §4 says so explicitly rather than estimating one — that is a property of a past window and no later work changes it. What changed is §5, the prospective half:
- Items 1–2 are no longer blocked outside this repo. The 2026-08-22 audit said "a refused write never enters the graph, so that count lives with quipu's refusal path". It does, and quipu built it: main
71440ffrecords every gate refusal (shacl|policy|authority|owl|placement) as a durablewrite.refusedevent carrying graph/actor/source/reason/refused_datums, served byGET /events?types=write.refusedand counted byquipu events refusals. A queryable denominator source exists. Item 1 is built:scripts/refusal_denominator.py(just refusal-rate) is that query, joined to the acceptedVerificationpopulation and needing no new camayoc term. It reports and never writes — a rate between two moving populations is a read-time judgment, not a fact true at write time. Building it corrected this section twice:reasonnames the GATE, not the failing shape, so "refused for a missing falsifier" is an upper bound rather than a count; and the refusal stream is prospective while the accepted population is the whole resident graph, so the share is a floor for that reason as well as forspeculate. What is still unmeasured is a real window: the instrument exists, nothing has run through it yet, and a number will only mean something once one has.- Item 2 stays deferred, for a different and correct reason. An A1–A7 controlled vocabulary on refused verifications is now buildable and still must not be minted: no competency question asks for per-form refusal counts, and competency-before-classes is not suspended because a dependency cleared. The question comes first or the term does not come at all. This is a deliberate hold, not an unbuilt item.
- Item 3 is built.
scripts/ingest_session_usage.py(just ingest-usage) parses a harness's own session logs into the §D vocabularycamayoc-e29minted, so Q16–Q21 run against a live window instead of only against the fixture. Measured while building it: the unit of consumption is the API request, not the log entry — see §5.Tracked as
camayoc-0d3rather than left as prose. (§4.2's own lesson bit this repo once more meanwhile: the coverage tool's verification denominator was itself undercounted until 2026-08-22 — six §D questions were missing from it, and four of the suite's six SLICES — 56 questions — until 2026-08-25, when it stopped being a lesson anyone restates and became a test.)
Source of record: ../../competency/verification-and-liveness.md,
written during and immediately after the night of 2026-08-06/07 on a live
crew. This document turns that prose into a labelled corpus with the taxonomy
as categories, per camayoc-101.
1. Why this is the paper's strongest asset
Papers about verification integrity are usually arguments. This one has instances: eleven separate checks that could not have failed, observed in one night, on a running multi-agent crew, recorded by the people who were fooled by them rather than reconstructed afterwards by someone looking for examples.
The unifying observation, and the sentence the paper should be built on:
The night's dominant failure was not a wrong answer. It was a check performed against something adjacent to the claim, reported as verification, by careful people.
"By careful people" is load-bearing. Nothing here is explained by negligence, which is what makes it a systems problem and therefore an admission-gate problem.
2. Category A — verification integrity
Seven recurring forms. Each was observed at least once; the eleven instances distribute across these seven forms (per-form counts are not recoverable — see §4.2).
| ID | Form | What was checked | What was claimed | The adjacency |
|---|---|---|---|---|
| A1 | Redundancy that wasn't | One guard present and working | "Belt and braces — there's a second mechanism" | The documented backup did not exist. The single point of failure was documented as a redundancy. |
| A2 | Right test, wrong mode | Byte identity of a file | "The file is unchanged on disk" | The comparison ran against a read-write open, so it compared the buffer to itself. |
| A3 | Partial-key comparison | Schema (type, name) | "The schema matches" | ADD COLUMN changes neither type nor name of existing columns, so the whole class of additive migration was invisible to the check. |
| A4 | Branches that don't branch | A binding condition with two arms | "Both paths are covered" | Both arms called the same function. The conditional was decorative. |
| A5 | Matching the description, not the thing | grep -c over a test payload | "The bug signature is present/absent" | The payload contained a description of the bug. The grep matched the prose, not the behaviour. |
| A6 | Zero-from-nothing | grep over a capture | "Absent — count is 0" | The capture was empty. A 0 from an empty input and a 0 from a populated one are different facts; the check could not tell them apart. |
| A7 | Corroboration that doesn't discriminate | Evidence consistent with hypothesis H1 | "This confirms H1" | The same evidence was equally consistent with H2. It had no discriminating power between the live hypotheses. |
The falsifier test applied to each. For every row, ask the constraint camayoc enforces at ingress — what result would have proved this wrong, and could the check have produced that result?
- A1, A4: no. The check's output was independent of the property claimed.
- A2, A6: no, given the conditions on the night. Under other conditions (read-only open; non-empty capture) the same check is sound. This is the distinction competency question #5 preserves deliberately: a check in an adjacent setting is valid exactly when the mechanism under test does not depend on the variable that changed. These two are not bad checks; they are sound checks run outside their preconditions, which the falsifier field would have surfaced by forcing the precondition to be named.
- A3: partially — it could fail for a renamed or retyped column, never for an added one. A falsifier scoped narrower than the claim. The most insidious form, because the check does work sometimes.
- A5, A7: no. Both could return "confirmed" under the negation of the claim.
This is the paper's argument in miniature and it should be presented exactly
this way. Not "these were bad checks" — a reader nods and moves on — but
"here are seven checks, and the single question what would have falsified this? separates all seven from verification, mechanically, without hindsight."
A3 in particular is where the mandatory falsifier field earns its keep: a
human writing "renamed or retyped column" into the field has, in that act,
noticed that ADD COLUMN is not covered.
3. Category B — held-work liveness, and Category C — armed ≠ merged
B: four mechanisms, one missing predicate. Four P1s open simultaneously, one root cause: every routing mechanism recorded an owner without checking the owner was alive.
| Mechanism | Recorded | Never checked |
|---|---|---|
| Bead assignment | assignee | assignee running? |
| Alert keeper labels | keeper | keeper running? |
BLOCKED status | blocker | blocker still real? |
in_progress status | holder | holder running? |
Consequence identical in all four, and worst-possible: a thing correctly detected and correctly recorded is indistinguishable, from every dashboard, from a thing that was never detected at all.
Measured durations: a P1 blocked 14 days on a dependency that had already cleared; a retraction that never propagated for 14 days; a CI break unread for 6 hours across 12 production deploys.
The sharpest instance is recursive and should be quoted verbatim in the paper: the bead documenting invisible in-progress work was itself sitting in-progress, invisible, on a stopped agent.
C: armed ≠ merged ≠ correct. A guard correct in git and absent from every execution path for 24h; a config feature landed, tested, and inert until armed; a rules file six weeks stale reaching agents while the current one sat in the repo; 17 host execution paths running scripts out of individual working trees.
B and C are the same defect at different layers: a fact recorded at write time that silently decays. They are why ingress rule 5 refuses to store decaying judgments at all, and why liveness is a read-time join rather than a stored predicate.
4. The two attacks, answered
camayoc-101 names both, and they are the right attacks.
4.1 Selection bias: were these found by looking for them?
Partly, and the corpus must say so.
Honest reconstruction of how the instances surfaced:
- The Category B family was not sought. Four P1s were open simultaneously and the common cause was noticed because they collided. This is closer to a census than a search: the population is "P1s open that night", and the discovery was of a shared cause, not of instances of a pattern.
- The Category A instances were partly sought. Once A1 was found — a documented redundancy that did not exist — the crew began asking the falsifier question of other checks, and found more. The first instance was incidental; later ones were elicited by a search.
This matters and cuts a specific way. A corpus assembled by searching for a pattern demonstrates that the pattern is findable, not that it is common. So the corpus supports:
✅ "When we asked what would have falsified each check, checks failed that question at a rate that surprised us, across seven distinct forms."
and does not support:
❌ "N% of verifications in this codebase are unfalsifiable."
The paper must make exactly the first claim. The second requires a denominator we do not have.
4.2 Count versus rate: eleven out of what?
The denominator is not recoverable from the record as kept. Stating this plainly is the only defensible option, and it is consistent with the paper's own thesis: a claim must name what would falsify it, and "eleven instances" names nothing.
What is anchored:
| Quantity | Value | Confidence |
|---|---|---|
| Instances of unfalsifiable verification | 11 | Recorded contemporaneously |
| Distinct forms | 7 | Recorded |
| Window | one night, 2026-08-06/07 | Recorded |
| Simultaneous P1s sharing one root cause | 4 | Recorded |
| Routing mechanisms missing the liveness predicate | 4 of 4 examined | Recorded — this one is a rate |
What is not anchored, and must not be estimated:
- Total verifications performed that night. Not counted. Without it there is no "11 out of N".
- Per-form counts. Eleven instances across seven forms; the distribution was not recorded.
- Crew size and agent-hours. Not captured in the record, so no per-agent or per-hour normalisation.
The one genuine rate in the corpus is Category B: 4 of 4. Every routing mechanism examined lacked the liveness predicate — a complete census of a small, enumerable population, with no selection step, because the four were identified by their collision rather than by a search. That is the number the paper should lead with when a rate is demanded, and it should be presented for exactly what it is: a small denominator, completely enumerated.
5. What would fix this for the next window
Concrete instrumentation, so the gap is a finding with a remedy rather than an apology:
-
Count the denominator at ingress. ✅ Built (2026-08-25). Once
Verificationis a shape-gated class, every accepted verification is a stored fact — that half has been true since aspect 4. The missing half was the refusals, and a refused write never enters the graph, so for a year the honest statement here was "this count lives somewhere camayoc cannot reach". It now lives somewhere camayoc can query: quipu records each gate refusal as a durablewrite.refusedevent with graph/actor/source/reason/refused_datums, served byGET /events?types=write.refused. Total accepted, total refused for a missing falsifier, and the ratio are a join away, and the refusal count is itself the rate this corpus lacks — measured prospectively, with no search step. Two boundaries the event stream states about itself and this document must repeat rather than smooth over: refusals insidespeculateare excluded, and the refused fact bodies are deliberately not stored, so the stream answers how many and why, not what was rejected. A per-form count therefore cannot be recovered from the refusal record alone — which is what item 2 is about.scripts/refusal_denominator.py(just refusal-rate) is that join. It reports and never writes: a refusal rate is a ratio between two populations that both keep moving, which makes it a judgment computed at read time rather than a fact true at write time, and storing it would store something that starts decaying with the next write. 37 tests intests/test_refusal_denominator.py, most of them abstentions — a store that could not be reached, an event envelope in an unrecognised shape, and an/askanswer carrying no count all exit 3 rather than rendering as zero refusals, which isgate_probe.sh's distinction applied to the same failure one layer up.Two corrections this section owes to having built the thing. First, "total refused for a missing falsifier" is not a join away and this document said it was. The stream's
reasonfield names the GATE that refused —shacl | policy | authority | owl | placement— not the shape that failed, and the bodies are gone. So the shacl total is an upper bound on the missing-falsifier count and there is no path from this source to the count itself. The reporter prints it as an upper bound and names no narrower quantity; any narrowing is an operator-supplied--reason-containsfilter, reported as the operator's classification. Second, the two populations do not share a time origin, which is §4.2's own defect at a new scale: refusals are a prospective stream that began when quipu started recording them, while acceptedVerifications are the resident graph — every write ever made, the pre-gate legacy population included. The denominator is therefore too large and the share too small. That makes the reported share a floor in three independent ways, and the report states all three on every run rather than in a footnote. -
Record the form. ⛔ Deliberately not built, and not for want of a dependency. A controlled vocabulary over A1–A7 on refused verifications would give per-form counts directly. No competency question asks for them. Camayoc's first convention is competency-questions-before-classes — no ontology term without a question in
competency/that needs it — and that rule does not lapse because the blocking dependency cleared. Minting A1–A7 now would be minting a taxonomy this document happens to contain, for a query nobody has written, which is exactly the failure the rule exists to prevent. The question comes first, then the terms. Until someone writes it, §4.2's missing per-form distribution stays missing and stays stated. Item 1 landing does not change this: the reporter built for it deliberately names no per-form quantity, and its own test suite asserts that no refusal-form vocabulary exists inontology/core.ttl— so a future session reaching for the terms trips a test before it reaches the ontology. -
Normalise by agent-hours. ✅ Built (2026-08-25). Both harnesses already write complete per-session token accounting to local disk, so this is a deterministic-parser fact (ingress rule 3) and needed no new plumbing.
scripts/ingest_session_usage.py(just ingest-usage) is that parser: it emitsaegis:Sessionandaegis:UsageRecordin the vocabularycamayoc-e29minted, taggedobserved, so Q16–Q21 run against a live window rather than only against the fixture.Two findings from building it, both of which the format description would not have told anyone. First, the unit of consumption is the API request, not the log entry: the claude harness writes one entry per content block and repeats the whole turn's
usageobject on every one of them. Measured against a real session — 237 entries carrying usage, 92 distinct requests, the usage object identical across each group — the naive per-entry sum reports 39,467,766 tokens for a session that consumed 15,653,391. A 2.5x overcount, in the direction that flatters a throughput claim and inflates a spend one, and it would have looked entirely plausible. Second, only the claude reader is verified, because no real codex rollout file was available to measure one against; codex logs are counted as unrecognised in the run's denominator and emit nothing, since a parser written from a prose description of a format is a guess, and the first finding is precisely what such a description omits.
Note the shape of this: the mechanism the paper proposes is also the instrument that would have measured the problem it was built for. That is worth one sentence in the paper and no more — it is a genuine property of the design, and overselling it would be its own unfalsifiable claim.
6. How to use this corpus in the paper
- Lead with A1–A7 and the falsifier question applied to each. It is concrete, it is mechanical, and it requires no hindsight.
- Use B's 4-of-4 as the rate, with the small enumerated denominator stated.
- Put §4 in the paper, not in an appendix. A corpus that names its own selection bias and its own missing denominator is more credible than one that does not, and this paper in particular cannot argue that claims must be falsifiable while making an unfalsifiable one about its own evidence.
- State that the LIVENESS corpus motivates the mechanism rather than
validating it. As of later on 2026-08-22 the scope of that sentence
narrowed: the cost/denominator family (§D, Q16–21) and the section-A
single-edge questions (Q3/Q4/Q5/Q7/Q12) run as stored queries
(
camayoc-89e,camayoc-e29; 16/19 on that slice, up from 5/19 that morning — the "4 of 47" this bullet cited on 2026-08-17 predates the golden-paths slice's 12/16). What still does not exist is the liveness-join family (#6, #8, #9) — the four-beads-one-cause questions themselves — which remain GAP rows injust query-coverage; seeimplemented-set.md§2. For section B the corpus is still the problem statement, with the evaluation to follow.
Docs map
Every document in this repository that is not a page of this book.
| document | what it is |
|---|---|
skills/camayoc/SKILL.md | the skill agents load: the four moves (bootstrap, query first, record at the moment, tag honestly) |
commands/bootstrap.md | the /camayoc:bootstrap slash command |
competency/*.md | the competency questions the graph must answer, one file per slice |
AGENTS.md, CLAUDE.md | instructions for agents working on this repository |
.beads/README.md | the repository's own issue tracker |
Design notes record how a feature was decided and may describe an earlier
state; they are in this book under Design notes, and incident-corpus.md
and implemented-set.md are historical records.