Skip to content

HTTP API: knowledge_base

What the knowledge deriver left for review and whether it is still writing: the candidate records awaiting promotion with the branch of the episode each came from, one record by id with every reference it names resolved against the index, the ledger and git, and the derivation status of this checkout judged against the policy's freshness thresholds. Read from the index and the ledger; written by nothing here — the deriver is the shell tool's, and a person promotes.

Rendered from docs/generated/openapi.json, which the executable generates from its capability registry and CI refuses stale. The raw document is at /openapi.json; a running server serves the same document at its own /openapi.json and Swagger UI at /swagger.

knowledge_base

What the knowledge deriver left for review and whether it is still writing: the candidate records awaiting promotion with the branch of the episode each came from, one record by id with every reference it names resolved against the index, the ledger and git, and the derivation status of this checkout judged against the policy's freshness thresholds. Read from the index and the ledger; written by nothing here — the deriver is the shell tool's, and a person promotes.

GET /api/v1/knowledge/candidates knowledge_base.candidates

Candidates awaiting review

Every knowledge record with status candidate under the candidates source class: its class, its assertion, the episode it came from and that episode's branch, and how long it has waited — measured from the derivation that wrote it, then the commit that added it, then its own date — judged against session.freshness; against the policy's cap. A candidate whose episode this checkout does not know is reported with no branch rather than hidden. Ordered by id.

  • query
  • behaviorally_verified
  • benchmark required
  • cache disabled
  • MCP tool majordomus_knowledge_candidates
  • MCP resource {"name": "knowledge-candidates", "uri": "majordomus://knowledge-candidates"}
  • $ knowledge candidates

No parameters.

Try it

The port is whatever the server logged; 8741 is the one it asks for first. The values are the operation's first example.

curl -s "http://127.0.0.1:8741/api/v1/knowledge/candidates"

Tested by apps/majordomus-cli/tests/http_serve.rs, which replays the route with these values against a server on a real socket.

Responses

statusbodymeaning
200 KnowledgeCandidates Candidates awaiting review: the result.
400 ErrorBody invalid_input: The input does not fit the schema: a parameter of the wrong type, an unknown one, or a value the capability rejects.
404 ErrorBody not_found: The input names something the repository does not hold.
500 ErrorBody internal: The capability failed for a reason of its own; the message names it.
default ErrorBody Any other failure the transport reports: 405 method_not_allowed for another method on this path, 400 invalid_input for a body that is not a JSON object.
GET /api/v1/knowledge/record knowledge_base.record

One knowledge record

One knowledge record by id — candidate or curated — with every reference it names resolved: a session against the tracked session records and the ledger, a task or decision against the ledger and the task store, a commit against git, a file or test against the index and the tree, a rule, an ADR, an issue or another record against the index. Which references dangle is the question a reviewer asks before promoting, and the one the integrity validator asks of every record. A `task:none` or `decision:none` reference is refused by name.

  • query
  • behaviorally_verified
  • benchmark required
  • cache disabled
  • MCP tool majordomus_knowledge_record
  • $ knowledge record

Query parameters

nametyperequireddescriptionexamples
id string yes The record id, as the file name and `majordomus://knowledge/<id>` carry it. first-record: "a-commit-stood-behind-a-build-lock"

Try it

The port is whatever the server logged; 8741 is the one it asks for first. The values are the operation's first example.

curl -s "http://127.0.0.1:8741/api/v1/knowledge/record?id=a-commit-stood-behind-a-build-lock"

Tested by apps/majordomus-cli/tests/http_serve.rs, which replays the route with these values against a server on a real socket.

Responses

statusbodymeaning
200 KnowledgeRecord One knowledge record: the result.
400 ErrorBody invalid_input: The input does not fit the schema: a parameter of the wrong type, an unknown one, or a value the capability rejects.
404 ErrorBody not_found: The input names something the repository does not hold.
500 ErrorBody internal: The capability failed for a reason of its own; the message names it.
default ErrorBody Any other failure the transport reports: 405 method_not_allowed for another method on this path, 400 invalid_input for a body that is not a JSON object.
GET /api/v1/knowledge/status knowledge_base.status

Whether the deriver is still writing

The freshness half of the stopped-writer judgement (ADR 0052, applied to the knowledge deriver by ADR 0091): the newest knowledge.derived line, the newest session.closed line, how many closed episodes no derivation names, the candidate counts, and — once a derivation has run in this checkout and the switch is on — whether the newest closed episode went underived past session.freshness.stale_minutes. Episode ids are compared, never line order. Whether the close path still calls the deriver is read from the source by the shell validator, not here.

  • query
  • behaviorally_verified
  • benchmark required
  • cache process (max_entries=2, ttl_seconds=2)
  • MCP tool majordomus_knowledge_status
  • MCP resource {"name": "knowledge-status", "uri": "majordomus://knowledge-status"}
  • $ knowledge status

No parameters.

Try it

The port is whatever the server logged; 8741 is the one it asks for first. The values are the operation's first example.

curl -s "http://127.0.0.1:8741/api/v1/knowledge/status"

Tested by apps/majordomus-cli/tests/http_serve.rs, which replays the route with these values against a server on a real socket.

Responses

statusbodymeaning
200 KnowledgeStatus Whether the deriver is still writing: the result.
400 ErrorBody invalid_input: The input does not fit the schema: a parameter of the wrong type, an unknown one, or a value the capability rejects.
404 ErrorBody not_found: The input names something the repository does not hold.
500 ErrorBody internal: The capability failed for a reason of its own; the message names it.
default ErrorBody Any other failure the transport reports: 405 method_not_allowed for another method on this path, 400 invalid_input for a body that is not a JSON object.

Schemas

The components only this tag's operations use. Each row is the type's own field, its doc comment the description; a type another page owns links there.

AgeSource

"derivation" | "commit" | "date"
Where the age of a candidate in the review queue was read from. A candidate's date is the day of the evidence it came from, deliberately, so that the same evidence yields the same bytes; the day it entered the queue is a different fact and is read from the ledger first, then from git, and only then from the record.

use majordomus_cli::capability::builtin::knowledge_base::AgeSource;
let from_ledger: AgeSource = serde_json::from_str(r#""derivation""#).unwrap();
assert_eq!(from_ledger, AgeSource::Derivation);
assert!(AgeSource::Derivation < AgeSource::Date, "the ledger is asked before the record");
  • derivationThe oldest knowledge.derived line in this checkout's ledger whose paths name it.
  • commitThe commit that added the file to this history.
  • dateThe record's own date, at midnight UTC: the evidence day, not the queue day.

Used by KnowledgeCandidate

KnowledgeCandidate

object
One candidate record awaiting review: what a reader needs to decide whether to open it.

use majordomus_cli::capability::builtin::knowledge_base::KnowledgeCandidate;
let c: KnowledgeCandidate = serde_json::from_str(r#"{
  "id": "e1-0123456789ab", "uri": "majordomus://knowledge/e1-0123456789ab",
  "path": ".ai/repo/knowledge/candidates/e1-0123456789ab.md", "class": "convention",
  "status": "candidate", "epistemics": "decided", "title": "Tests run in disposable repositories",
  "date": "2026-09-12", "derived_from": ["session:e1"], "episode": "e1",
  "freshness": "unknown", "freshness_reason": "no timestamp", "age_source": "date"
}"#).unwrap();
assert_eq!(c.episode, "e1");
assert_eq!(c.branch, None, "an episode this checkout never saw has no branch, and says so");
propertytyperequireddescription
id string yes The record id, which is also its file name.
uri string yes majordomus://knowledge/<id>.
path string yes Repository-relative path.
class string yes fact, convention, constraint, memory or lesson.
status string yes Always candidate here; carried so that a reader sees the same record shape as record.
epistemics string yes observed, inferred or decided.
title string yes The assertion, in one line.
date string yes The day of the evidence, YYYY-MM-DD.
derived_from array of string no The typed references the record names as its evidence, as declared.
episode string no The episode the record came from: the session: reference, or empty when it names none.
branch string | null no The branch that episode ran on, joined to the tracked session record or, while that is not yet written, to the ledger's session.started line. None when this checkout knows nothing about the episode — named rather than hidden, so that a reader can tell "not this branch" from "not known here". Serialised as null in that case, never omitted.
freshness Freshness → Freshness yes How long it has waited, judged against session.freshness.
age_minutes integer | null no The wait in whole minutes, when it could be measured.
freshness_reason string yes Why it was judged that way, in one phrase. Never empty.
age_source AgeSource → AgeSource yes Where the wait was read from.
about string no What the observation it was derived from is about (ADR 0118); empty for a record derived from anything else.
route string no Which owner should look first, derived from about's structure; empty without one.

Used by KnowledgeCandidates

KnowledgeCandidates

object
The candidates awaiting review, against the policy's cap.

use majordomus_cli::capability::builtin::knowledge_base::KnowledgeCandidates;
let empty: KnowledgeCandidates = serde_json::from_str(
    r#"{"total":0,"on_this_branch":0,"unattributed":0,"branch":"master","candidates":[],"over_cap":false}"#,
).unwrap();
assert_eq!(empty.total, 0, "an empty queue is an answer");
assert_eq!(empty.cap, None, "and a policy without the key declares no cap");
propertytyperequireddescription
total integer (uint) yes How many records carry status: candidate under the candidates class.
on_this_branch integer (uint) yes How many of them came from an episode on this checkout's branch.
unattributed integer (uint) yes How many name an episode this checkout knows nothing about.
branch string yes The branch this answer is about, or DETACHED.
candidates array of KnowledgeCandidate → KnowledgeCandidate no Every candidate, in canonical order of id.
groups array of KnowledgeGroup → KnowledgeGroup no The candidates and rejected records derived from observations, grouped by what they are about, in order of subject.
cap integer | null no knowledge.candidates_max_files, when the policy declares it.
over_cap boolean yes True when total exceeds cap. False when no cap is declared: nobody wrote one down.
findings array of string no What a reader should know before trusting the above: a malformed record that was skipped, a cap that was exceeded, a policy that declares none. Empty is the good case.

Used by knowledge_base.candidates

KnowledgeGroup

object
The records under one subject: every candidate awaiting review and every rejected one, with the episodes behind them (ADR 0118). Recurrence is read here, never written: each record stays one episode's own file, and a group is how several of them read as one thing.

use majordomus_cli::capability::builtin::knowledge_base::KnowledgeGroup;
let g: KnowledgeGroup = serde_json::from_str(
    r#"{"about":"lib/a.sh","route":"project","candidates":["e1-a","e2-b"],"episodes":["e1","e2"]}"#,
).unwrap();
assert_eq!(g.episodes.len(), 2, "two episodes observed it");
assert!(g.rejected.is_empty(), "and nobody rejected it");
propertytyperequireddescription
about string yes The subject, as written.
route string yes The route of its records.
candidates array of string no The candidates awaiting review, by id.
rejected array of string no The records rejected and kept, by id.
episodes array of string no The episodes behind all of them, each once, in order.

Used by KnowledgeCandidates

KnowledgeRecord

object
One knowledge record, with every reference it names resolved.

objects.get already serves the same file as metadata and content. What this adds is the resolution: which of the record's references answer, through which reader, and which dangle — the question a reviewer asks before promoting a candidate, and the question the integrity validator asks of every record.

use majordomus_cli::capability::builtin::knowledge_base::KnowledgeRecord;
let r: KnowledgeRecord = serde_json::from_str(r#"{
  "id": "e1-0123456789ab", "uri": "majordomus://knowledge/e1-0123456789ab",
  "path": ".ai/repo/knowledge/candidates/e1-0123456789ab.md", "source_class": "candidates",
  "class": "convention", "status": "candidate", "epistemics": "decided", "origin": "extracted",
  "date": "2026-09-12", "title": "An assertion", "resolved": true, "content": "---\n---\n"
}"#).unwrap();
assert!(r.derived_from.is_empty(), "a record that names nothing has nothing to resolve");
assert_eq!(r.superseded_by, None, "and is not superseded");
propertytyperequireddescription
id string yes The record id.
uri string yes majordomus://knowledge/<id>.
path string yes Repository-relative path.
source_class string yes The source class that discovered it: candidates or curated.
class string yes fact, convention, constraint, memory or lesson.
status string yes candidate, verified or superseded.
epistemics string yes observed, inferred or decided.
origin string no authored or extracted; empty when the record carries no provenance.
date string yes The day the record reached its status.
title string yes The assertion.
derived_from array of ResolvedReference → ResolvedReference no Every provenance.derived_from reference, resolved, in the record's order.
relations array of ResolvedRelation → ResolvedRelation no Every relations entry, resolved, in the record's order.
superseded_by string | null no The record that replaced this one, when the status is superseded.
resolved boolean no True when every reference above resolved. The one bit a reviewer reads first.
content string yes The whole file, front matter and body.

Used by knowledge_base.record

KnowledgeStatus

object
Whether the deriver is still writing, and what it last wrote.

The judgement is ADR 0052's, applied to this writer: while episodes keep closing, a derivation follows every close. It compares episode ids, never line order or timestamps between the two events, because the close derives before it appends session.closed. It is the freshness half of the judgement only; whether the close path and the compaction adapter still call the deriver is read from the source by the shell validator, which is where the source is.

use majordomus_cli::capability::builtin::knowledge_base::KnowledgeStatus;
let fresh: KnowledgeStatus = serde_json::from_str(r#"{
  "present": false, "branch": "master", "closed_without_derivation": 0, "candidates": 0,
  "on_this_branch": 0, "freshness": "unknown", "freshness_reason": "no episode has closed here",
  "judged": false, "stopped_writer": false, "knowledge_on_end": true, "knowledge_on_compact": true
}"#).unwrap();
assert!(!fresh.present, "a fresh clone has no ledger, and that is not a fault");
assert!(!fresh.stopped_writer);
propertytyperequireddescription
present boolean yes Whether anything is here to judge: a ledger line or a candidate. False in a fresh clone.
branch string yes The branch this checkout is on, or DETACHED.
last_derived LedgerMark | null → LedgerMark no The newest knowledge.derived line, or None when the deriver has never run here.
last_closed LedgerMark | null → LedgerMark no The newest session.closed line, or None when no episode has closed here.
closed_without_derivation integer (uint) yes How many closed episodes in this ledger no knowledge.derived line names.
candidates integer (uint) yes How many records carry status: candidate under the candidates class.
on_this_branch integer (uint) yes How many of those came from an episode on this branch.
freshness Freshness → Freshness yes How old the newest closed episode is, judged against session.freshness.
age_minutes integer | null no That age in whole minutes, when it could be measured.
freshness_reason string yes Why it was judged that way. Never empty.
judged boolean yes Whether the stopped-writer judgement was made at all. False, with the reason among findings, while no episode has closed here, while the switch is off, or while no derivation has yet run in this checkout.
stopped_writer boolean yes The finding: episodes close here and no derivation followed the newest one within the stale threshold. True only when judged.
knowledge_on_end boolean yes session.knowledge_on_end, as the policy declares it; absent means on.
knowledge_on_compact boolean yes session.knowledge_on_compact, likewise.
findings array of string no What a reader should know: why the judgement was withheld, or what it found.

Used by knowledge_base.status

LedgerMark

object
One ledger line that matters to the judgement: when, and which episode.

use majordomus_cli::capability::builtin::knowledge_base::LedgerMark;
let m: LedgerMark = serde_json::from_str(r#"{"ts":"2026-09-12T10:00:00Z","episode":"e1"}"#).unwrap();
assert_eq!(m.episode, "e1");
propertytyperequireddescription
ts string yes The line's timestamp, RFC 3339 in UTC.
episode string no The episode the line is about; empty when the line names none.

Used by KnowledgeStatus

ReferenceResolution

"object" | "external" | "historical" | "missing"
How a typed reference resolved.

Four words and no fifth: a reference names an object of the index, or something only the ledger and git can vouch for, or a file the tree no longer holds and git history does, or nothing. external is not a weaker object — a commit or a task is exactly as real as a rule — it says which reader answered; historical resolves as well, and is named apart so a reader sees which provenance outlived its file (I1987).

use majordomus_cli::capability::builtin::knowledge_base::ReferenceResolution;
let r: ReferenceResolution = serde_json::from_str(r#""external""#).unwrap();
assert_eq!(r, ReferenceResolution::External);
assert!(r.resolves(), "vouched for by the ledger or git is resolved");
assert!(!ReferenceResolution::Missing.resolves());
  • objectAn object of the index: the target is its URI, or its path for a file.
  • externalVouched for by the ledger, the task store or git: a session, a task, a decision, a commit.
  • historicalA file: or test: the tree no longer holds and git history does: cited before the file was deleted, or pinned as file:<path>@<commit>. The target is the blob that answers, <commit>:<path>, which git show reads.
  • missingNothing answers to it. A dangling reference is the integrity validator's finding.

Used by ResolvedReference ResolvedRelation

ResolvedReference

object
One derived_from reference of a record, with how it resolved and against what.

use majordomus_cli::capability::builtin::knowledge_base::{ReferenceResolution, ResolvedReference};
let r: ResolvedReference = serde_json::from_str(
    r#"{"reference":"task:none","resolution":"missing","reason":"`none` is not a task"}"#,
).unwrap();
assert_eq!(r.resolution, ReferenceResolution::Missing);
assert_eq!(r.target, None);
propertytyperequireddescription
reference string yes The reference as written: <prefix>:<value>.
resolution ReferenceResolution → ReferenceResolution yes How it resolved.
target string | null no What it resolved to: a URI for an object, a path for a file, the reader that vouched for an external reference. Absent when nothing did.
reason string no Why it did not resolve, when it did not.

Used by KnowledgeRecord

ResolvedRelation

object
One relations entry, resolved the same way.

use majordomus_cli::capability::builtin::knowledge_base::{ReferenceResolution, ResolvedRelation};
let r: ResolvedRelation = serde_json::from_str(
    r#"{"relation_type":"relates_to","reference":"file:docs/CLI.md","resolution":"object","target":"docs/CLI.md"}"#,
).unwrap();
assert_eq!(r.relation_type, "relates_to");
assert!(r.resolution.resolves());
propertytyperequireddescription
relation_type string yes relates_to, depends_on, documents, supports, contradicts, supersedes or derived_from.
reference string yes The target as written.
resolution ReferenceResolution → ReferenceResolution yes How it resolved.
target string | null no What it resolved to, when something did.
reason string no Why it did not resolve, when it did not.

Used by KnowledgeRecord

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