Skip to content

majordomus knowledge

The compiled index over what this repository states, and the candidate records it derives from what an episode did.

The compiled index over what this repository states, and the candidate records it derives from what an episode did.

  • state-mutating
  • memory
  • --json

Discovery reads the version-control index with the declared pathspecs and never walks the tree. Nodes keep their identity across a rebuild, kinds are never guessed, and an edge exists only where a file states it. sources, nodes, edges, candidates and check write nothing. search, show and related look up the same nodes and edges and write nothing either: there is no stored index to go stale, so a lookup compiles what the checkout holds; search places each node on the first rung of a fixed ladder it reaches (id, title, title-prefix, path, body) and names the rung (I0811). validate judges the graph itself — duplicate ids, edges with a missing end or no provenance, declared paths and tracked sources that no longer exist — failing on a broken relationship Majordomus defines and only reporting a broken link an author wrote; doctor and watch ask the same validator through majordomus.knowledge-graph (I0812). derive turns the ledger lines one episode stamped into candidate records under the tracked knowledge section, deterministically and without a model; a second run over the same evidence writes nothing and says so. promote and reject are a person's acts on a candidate and each leaves a ledger line; nothing here stages a file (ADR 0091). observe records what a session met as one observation.recorded ledger line, refusing a kind, subject or evidence reference of the wrong shape and a statement that spans lines or opens like a conversation (ADR 0118).

Syntax

majordomus knowledge <sources|nodes|edges|search|show|related|derive|candidates|promote|reject|check|validate|observe> [<query>|<id>] [--scope shared|operational|all] [--kind <kind>] [--type <type>] [--episode <id>] [--dry-run] [--class <class>] [--reason <why>] [--by <id>] [--subject <ref>] [--evidence <ref>] [--json]

Lifecycle

  1. Set up
  2. Begin
  3. Work
  4. Verify
  5. Conclude
  6. Inspect

Other commands at this stage:doctor, watch, history, search, doctrine, version, rules, bench, archive.

Reads

  • .ai/repo/knowledge/sources.yaml
  • share/knowledge-sources.yaml
  • .ai/local/state/
  • .ai/repo/knowledge/candidates/
  • .ai/repo/knowledge/curated/
  • .ai/local/state/ledger.jsonl
  • .ai/repo/policy.yaml
  • share/allow/knowledge.txt

Writes

  • .ai/repo/knowledge/candidates/
  • .ai/repo/knowledge/curated/
  • .ai/local/state/ledger.jsonl

Exit codes

Only the codes this command can produce. The full contract is on the commands index.

  • 0 ok
  • 2 usage
  • 10 contract unmet
  • 12 missing artifact
  • 13 internal error

What the repository states, compiled

knowledge discovers the declared source classes through the version-control index, derives one node per object it finds and one edge per relationship a file states, and reads no prose.

The declared sources, discovered

installed
yes

Given this repository

# A repository with Majordomus installed and projections generated, and one commit of work.
"$MJ" init >/dev/null
"$MJ" update >/dev/null
mkdir -p lib docs
echo a > lib/a
echo d > docs/d
git add . && git commit -qm base

Run

$ majordomus knowledge sources

Output contains

  • ^policy +shared +policy +[0-9a-f]{12} +.ai/repo/policy.yaml
  • ^knowledge sources: [0-9]+ file

exit 0

Every row is a tracked file matched by a declared pathspec; nothing on the filesystem is looked at unless a class names it.

Nodes of one kind

installed
yes

Given this repository

# A repository with Majordomus installed and projections generated, and one commit of work.
"$MJ" init >/dev/null
"$MJ" update >/dev/null
mkdir -p lib docs
echo a > lib/a
echo d > docs/d
git add . && git commit -qm base

Run

$ majordomus knowledge nodes --kind rule

Output contains

  • ^rule +shared +[0-9a-f]{12} +rule:majordomus.scope-integrity

exit 0

A rule's identity is the id in its front matter, so a rebuild after an edit keeps the node and every reference to it.

A subcommand that does not exist

installed
yes

Given this repository

# A repository with Majordomus installed and projections generated, and one commit of work.
"$MJ" init >/dev/null
"$MJ" update >/dev/null
mkdir -p lib docs
echo a > lib/a
echo d > docs/d
git add . && git commit -qm base

Run

$ majordomus knowledge bogus

Output contains

  • unknown subcommand

exit 2

Usage errors exit 2 and name the help text; nothing is guessed from a near miss.

What an episode would yield, without writing it

session open
yes

Given this repository

# Installed, with one execution episode open in this worktree.
. "$FIXTURE_SETUP/installed.sh"
"$MJ" session start --worker some-provider/some-model >/dev/null

Run

$ majordomus knowledge derive --dry-run

Output contains

  • ^knowledge derive:

exit 0

The deriver reads the open episode's ledger lines and says what it would write; with --dry-run no record and no ledger line is written, so it can be run anywhere.

Reference

A compiler over what this repository already states, and the writer of what an episode learned. sources, nodes, edges, search, show, related, candidates and check are read-only; derive, promote and reject write knowledge records and ledger lines, and nothing else.

It is not a wiki, not a database, not a memory service, and not a second place to write things down. Every source it reads is a file somebody already maintains, everything it produces is derived and regenerable, and none of it outranks the file it came from. A result is a pointer to canonical knowledge, never a rewritten answer.

knowledge is not search. search is a literal scan over the durable operational records — handovers, checkpoints, decisions, questions, prompts, the ledger — with no index, because that corpus is a handful of files. knowledge covers the repository's canonical artifacts: the policy, the profiles, the prompts, the milestone and issue contracts, the claims matrix and the documents. Two corpora, two contracts, and neither changes the other.

  • sources [--scope shared|operational|all] lists the curated source classes and the files each one discovers, with the class, the scope, the kind, a content hash and the repository-relative path.

  • nodes [--scope ...] [--kind <k>] derives one node per canonical object: its identity, its kind, the source it came from and that source's hash. Exits 10 when two objects claim one identity.

  • edges [--scope ...] [--type <t>] derives one edge per stated relationship, with the file and the field or line it was observed in.

  • search <query> [--scope ...] returns the nodes the query matches, in an order that is a rule rather than a score. A node is placed on the first rung it reaches: id (the query is its canonical id), title (the query is its title), title-prefix (its title begins with the query), path (its source path contains the query), body (its source file contains the query literally); every comparison ignores case. Within a rung the order is byte order of the id. Each result names the rung it was placed on (matched, and rung in --json), so the reason for the order is in the answer. Nodes carry no tags, so there is no tag rung.

  • show <id> [--scope ...] prints one node with its kind, scope, title, source path and content hash. Exits 12 when no node has that id.

  • related <id> [--scope ...] prints the edges out of and into one node, each with its type and the file and field or line it was observed in. Exits 12 when no node has that id.

    None of the three writes, and none rebuilds anything: there is no stored index to go stale, so each call compiles what the checkout holds at that moment, exactly as nodes does. Case 3037 asserts the ladder, the fields of every result, and that the working tree reads the same after all three.

  • derive [--episode <id>] [--dry-run] [--json] derives the knowledge the episode produced into candidate records under .ai/repo/knowledge/candidates/, from the ledger and git and never from a conversation: a recorded decision becomes a convention, a resolved question a fact, a blocked or failed task a lesson, a completion with a verification command a fact. The open episode, or the one named. Deterministic: same ledger, same git, same bytes, and a second run reports every record unchanged. --dry-run says what it would write and touches neither the tree nor the ledger.

  • candidates [--json] lists every record awaiting review — id, class, date, the branch of the episode it came from, title — reading the directory rather than the index, so a candidate the hook just wrote and nobody has added is listed.

  • promote <id> [--class <c>] < evidence.md moves a candidate to curated/ as verified, with the evidence on standard input appended under # Evidence and the class the person chose. The deriver never writes verified; this is the act that does.

  • reject <id> --reason "<why>" [--by <id>] marks a candidate superseded in place with the reason under # Rejected, naming the record that replaces it when --by gives one.

  • check [--json] validates every record under candidates/ and curated/: the schema, no unknown key, unique ids equal to the file name, provenance beside verified, every reference resolving, a reason beside superseded, no candidate claiming verified, no conversation. The same check check and doctor dispatch.

The read side of the same store is served by the executable — bin/majordomus-cli knowledge candidates, knowledge record <id> and knowledge status, each a capability declared once and answering identically over HTTP, MCP and the command line (CAPABILITIES.md); status reports the last derivation, the newest close and whether the writer has stopped. KNOWLEDGE.md has the derivation table, the two moments and the rules.

Writes: derive writes .ai/repo/knowledge/candidates/<id>.md, one per record, through a temporary file in the same directory renamed over the id-named file, never staged, and prints written, unchanged or skipped per record with the summary last; it appends knowledge.derived to the ledger — episode, counts, paths — even when it wrote nothing. promote writes .ai/repo/knowledge/curated/<id>.md the same way, removes the candidate, appends knowledge.promoted and prints the curated path last. reject rewrites the candidate in place and appends knowledge.rejected. The record id is the episode id and a digest of the evidence, so the same fact always lands in the same file.

Never stages, commits, or modifies any other file. Never reads a conversation, a prompt or a handover; never calls a model or a network. Never writes verified.

Refuses (10) promote with empty stdin, with stdin carrying a transcript marker, on a record whose status is not candidate, or with a class outside the enumeration; reject on a record whose status is not candidate, or when --by names no record; derive when the policy does not parse; check on any failing record. (12) promote or reject when no candidate carries the id. (2) reject without a non-empty --reason. (13) a write that did not complete. derive with no episode to derive from says so and exits 0.

$ majordomus decision add "Tabs are refused in the parser" --why "two encodings of one thing"
$ majordomus knowledge derive
written .ai/repo/knowledge/candidates/s-20260912101500-7f1a-3b9c2e4d1a05.md
knowledge derive: 1 written, 0 unchanged, 0 skipped for episode s-20260912101500-7f1a
$ majordomus knowledge derive
unchanged .ai/repo/knowledge/candidates/s-20260912101500-7f1a-3b9c2e4d1a05.md
knowledge derive: 0 written, 1 unchanged, 0 skipped for episode s-20260912101500-7f1a
$ printf 'Verified by reading lib/yaml.sh.\n' | majordomus knowledge promote s-20260912101500-7f1a-3b9c2e4d1a05 --class constraint
.ai/repo/knowledge/curated/s-20260912101500-7f1a-3b9c2e4d1a05.md

No edge without provenance. Every edge names where the relationship was stated, and an edge missing any of from, to, type or provenance is refused rather than emitted with a blank — a blank source reads as "unknown" and is indistinguishable from one nobody recorded. An edge nobody can trace to a line is not a fact, it is a guess wearing a fact's clothes, and it is a guess the person best placed to notice it is wrong will never see.

Nothing is inferred from prose. The repository already states its relationships explicitly, in fields somebody maintains, and those are both free and correct. The edge types are a closed set; an undeclared one is an error rather than a new vocabulary word:

typefrom → tostated in
part_ofissue → milestonethe issue's milestone
depends_onissue → issue, milestone → milestonethe record's depends_on
declaresmilestone → claimthe milestone's claims
specified_byclaim → documentdocs/CLAIMS.yaml's source
implemented_byclaim → implementationits implementation
tested_byclaim → test, doctrine → testits test
supportsdoctrine → claimthe doctrine's claims
referencesdocument → documentan inline Markdown link

Links are the one edge source that is not curated, and they are read conservatively. Fenced code is dropped first, because a path inside a code sample is an example of a path and not a reference to one. A target with a scheme, a protocol-relative target and an absolute path are all skipped; a fragment is trimmed, because docs/CLI.md#session refers to docs/CLI.md.

Severity distinguishes what Majordomus owns from what an author wrote. A declared relationship pointing at a file the repository does not contain is a FAIL: it is a broken promise. A link in a document pointing at nothing is a WARN: a document may deliberately point outside the repository and the target alone cannot tell the two apart. A relationship pointing at a real file that this compiler does not model as one node — a container of many objects, or something outside the curated set — is silent, because a report that is large by design is a report people stop reading.

nodes and edges are two views of one derivation, so a defect in either exits 10 on both. There is no clean node listing over a graph that is broken.

A node's identity is never its content hash. It is the object's own canonical id where it has one — claim:policy-parse, issue:I0801, milestone:M003, profile:debugging — and its repository path where it does not, as in document:docs/CONTINUITY.md. A hash says whether something changed; it can never say what something is, because then every edit would delete a node and create a stranger, and every reference to it would point at nothing without anything saying so. The hash rides along on the node and is what freshness is measured with.

The consequence is deliberate: editing a document keeps its node and moves its hash; renaming one is a delete and an add, and disturbs no other node.

A kind comes from structure, never from prose. It is decided by the source class the file was discovered in and by fields the file itself declares. Nothing reads a body looking for a word that suggests a type: a document that discusses roadmaps and milestones is a document that discusses them. Where no rule applies the kind is unknown and the node is still emitted, with one finding naming the class — an explicit unknown is information, and a confident wrong answer is not.

A title is taken or left empty, never invented. From the record's own title or description, or from a document's first level-one heading. A document with no heading gets no title rather than its filename, because a filename standing in for a title reads like a fact and is a guess.

Two stores have no id field of their own. A decision is keyed by its title and a question by its text — in both cases exactly what the ledger records and what a session envelope references, so three places name the same thing the same way and nothing maps between them. Answering a question rewrites the line it lives on, and the identity survives that: the appended answer is stripped, and only when what remains ends in the entry's opening date, so a question whose own text contains a dash is left whole rather than cut at a guess.

Discovery is driven by the repository index, not by a filesystem walk. A walk returns build output, vendored trees and editor droppings; it returns them in an order that differs between two machines; and it can return a file nobody meant to publish. Listing tracked files instead gives repository truth in a stable order, and an untracked file is never a source. Operational records are discovered from the state directory Majordomus itself owns under .ai/local/, which is never tracked — and from nowhere else. No hidden directory is scanned because it happens to exist.

The source classes are declared twice, by two owners: the repository declares its shared sources in its AI layer, .ai/repo/knowledge/sources.yaml, and the tool ships the operational classes, the records it writes itself, in share/knowledge-sources.yaml. The scope of a class is decided by which file declared it; neither file names the other:

scopemeaning
sharedrepository knowledge; may be projected to a public surface
operationalthis checkout's own working records; never part of a shared projection

The scope is a property of the class, so the publication boundary is decided in one place rather than at each producer. Every tracked pathspec carries the :(glob) prefix, under which * does not cross a directory separator. That is not tidiness: without it, docs/*.md also matches docs/claims/*.md, two classes silently overlap, and one file becomes two nodes. test/cases/64_knowledge_discovery.sh fails on that mutation.

One overlap is settled by the file rather than by the classes. A section's README.md matches both .ai/**/README.md, the context class, and its section's own pathspec (.ai/repo/features/*.md, .ai/repo/rules/**/*.md, and so on). It declares kind: context, so it is a context node, once: a second class discovering it adds nothing. The index applies the same precedence in apps/majordomus-cli/src/discovery/mod.rs, and test/cases/330_a_contract_is_the_kind_it_declares.sh proves it for knowledge nodes. Where no file declares a kind, two classes over one path remain a refusal (case 73's twin).

init declares the context class first, so a new repository's contracts are objects of its index from the start. Without it context resolve, which reads the files, and every capability that reads the index disagreed about what applies to a path; test/cases/130_context_compiler.sh fails without the class.

A class marked required that discovers nothing is reported as a WARN. The cost of a curated list is that a path can be forgotten, and a forgotten path is indistinguishable from a repository that does not have that file unless something says so.

Whether a file conforms to the schema of its kind is the executable's index's verdict, not the reader's: knowledge nodes asks the index which files it refused, drops them and reports each refusal as a FAIL (test/cases/405_the_knowledge_reader_honours_the_schema.sh). With no executable built, or no jq, it reports a WARN that the schema went unchecked; it never reads as a pass. MAJORDOMUS_KNOWLEDGE_SCHEMA=unasked says not to ask, and the reader then reports an INFO that the check was not asked for. That is what a recorded use-case scenario sets: whether the index could be asked is a fact about the recording machine, and evidence that must read the same on every machine cannot carry it (#713, test/cases/777_a_recorded_scenario_does_not_depend_on_the_recorder.sh).

$ majordomus knowledge sources --scope shared
policy      shared      policy     4f2a9c1d8b30  .ai/repo/policy.yaml
profile     shared      profile    a1b2c3d4e5f6  .ai/repo/profiles/debugging.yaml
...
issue       shared      issue      0e5a7b9c3f51  .ai/repo/project/issues/I0801.yaml
claims      shared      claim      7c9e1b3d5f70  docs/CLAIMS.yaml
document    shared      document   9b1e2d4f8c3a  docs/CONTINUITY.md
knowledge sources: 169 file(s) in scope shared

Evidence

Every case below runs in CI on Linux and macOS. The site refuses to build if a public command has no behavioural case, no negative case, or no demonstration.

Demonstration

Tested by test/cases/34_command_fixtures.sh, which replays the fixture and compares it with what the command prints.

Guarantees this command carries

Searches the titles and descriptions of every page this site publishes.