Skip to content

HTTP API: skills

Every skill the layer defines as a proven capability: whether a test names it and the evidence ledger holds a current passing run of that test, whether the site projects its page, whether a doctrine and a CI gate hold it, and whether a workflow, prompt, profile, provider template, recipe or CI workflow invokes it. All four are derived on every read and none is authored; an active skill no test names or nothing invokes is an orphan.

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.

skills

Every skill the layer defines as a proven capability: whether a test names it and the evidence ledger holds a current passing run of that test, whether the site projects its page, whether a doctrine and a CI gate hold it, and whether a workflow, prompt, profile, provider template, recipe or CI workflow invokes it. All four are derived on every read and none is authored; an active skill no test names or nothing invokes is an orphan.

GET /api/v1/skills skills.status

Every skill, with its derived standing

Every skill the index holds, each with the tests that name it and the evidence state of their latest runs, its page projection, the doctrine and gates that hold it, every invocation that references it, and the standing those derive: proven, partial, orphan, invalid, or not_required for a draft or deprecated skill.

  • query
  • behaviorally_verified
  • benchmark required
  • cache disabled
  • MCP tool majordomus_skills
  • MCP resource {"name": "skills", "uri": "majordomus://skills"}
  • $ skills 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/skills"

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 SkillStatusList Every skill, with its derived standing: 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/skills/explain skills.explain

One skill, with everything derived about it

One skill in full: its authored identity, status and provenance marker, each test that names it with the state of its latest recorded run and the command that reproduces it, its page, its doctrine and gates, every invocation by path and line, its standing and the findings about it. The file itself stays at `majordomus://skill/<id>`.

  • query
  • behaviorally_verified
  • benchmark required
  • cache disabled
  • MCP tool majordomus_skill_explain
  • $ skills explain

Query parameters

nametyperequireddescriptionexamples
id string yes The skill's id, which is also its directory name. first-skill: "assess-before-deleting"

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/skills/explain?id=assess-before-deleting"

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 SkillStatus One skill, with everything derived about it: 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/skills/verify skills.verify

What the skills owe and do not have

Every finding over the skills: a file that breaks the skill contract, an active skill no test names or nothing invokes, a test whose latest run failed, a test or an invocation naming a skill that does not exist — each a failure — and evidence that is not current, a missing page or a missing gate, each a warning.

  • query
  • behaviorally_verified
  • benchmark required
  • cache disabled
  • MCP tool majordomus_skills_verify
  • $ skills verify

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/skills/verify"

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 SkillVerification What the skills owe and do not have: 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.

SkillDocumented

object
The documented fact: the page the site projects for the skill.

use majordomus_cli::skill::SkillDocumented;
let d = SkillDocumented { documented: true, page: "site/content/skills/implement.md".into() };
assert!(d.page.ends_with("implement.md"));
propertytyperequireddescription
documented boolean yes Whether the page is a tracked file.
page string yes Where the page is projected.

Used by SkillStatus

SkillEnforced

object
The enforced fact: the doctrine that validates skills and the gates a change to this one selects.

use majordomus_cli::skill::SkillEnforced;
let e = SkillEnforced { enforced: false, doctrine: None, gates: vec![] };
assert!(!e.enforced);
propertytyperequireddescription
enforced boolean yes A doctrine validates skills, and a gate running the verification covers this file.
doctrine string | null no The rule whose validator is skills, when the index holds one.
gates array of string yes The CI gates a change to the skill selects whose command runs skills verify.

Used by SkillStatus

SkillEvidence

"proven" | "inputs_unchanged" | "stale" | "failing" | "not_run" | "unrunnable" | "untested"
What the evidence ledger can say about a skill's tests, as a whole: the weakest state of the tests that name it, or untested when none does.

use majordomus_cli::skill::SkillEvidence;
assert!(SkillEvidence::Proven.current());
assert!(SkillEvidence::InputsUnchanged.current());
assert!(!SkillEvidence::Stale.current());
assert!(SkillEvidence::Proven < SkillEvidence::Untested);
  • provenEvery naming test has a passing run, and nothing but the ledger changed since.
  • inputs_unchangedEvery naming test passed, and neither the test nor the skill changed since.
  • staleA passing run exists, and the test or the skill changed since.
  • failingThe latest recorded run of a naming test did not pass.
  • not_runA test names the skill and no run of it was ever recorded.
  • unrunnableA test names the skill from a path no runner drives.
  • untestedNo test names the skill.

Used by SkillTested

SkillFinding

object
One thing the derivation found wrong, with the command that settles or reproduces it.

use majordomus_cli::skill::{SkillFinding, SkillFindingLevel};
let f = SkillFinding {
    level: SkillFindingLevel::Fail,
    code: "untested".into(),
    skill: Some("implement".into()),
    subject: ".ai/repo/skills/implement/SKILL.md".into(),
    message: "no test names this skill".into(),
    reproduce: "majordomus skills explain implement".into(),
};
assert!(f.reproduce.contains("explain"));
propertytyperequireddescription
level SkillFindingLevel → SkillFindingLevel yes fail or warn.
code string yes A stable code: contract, untested, unused, unevidenced, failing, undocumented, unenforced, unknown_skill_in_test, unknown_skill_invoked.
skill string | null no The skill concerned, when the finding is about one.
subject string yes The file the finding is about, repository-relative.
message string yes What is wrong, and what to do about it.
reproduce string yes The command that reproduces or settles it.

Used by SkillStatus SkillVerification

SkillFindingLevel

"fail" | "warn"
How bad a finding is: a failure refuses, a warning is reported.

use majordomus_cli::skill::SkillFindingLevel;
assert_ne!(SkillFindingLevel::Fail, SkillFindingLevel::Warn);
  • failRefuses: skills verify exits 10 and the doctrine fails.
  • warnReported, and does not refuse.

Used by SkillFinding

SkillInvocation

object
One place a skill is invoked from.

use majordomus_cli::skill::SkillInvocation;
let i = SkillInvocation {
    path: ".ai/repo/prompts/review.md".into(),
    line: 3,
    reference: "majordomus://skill/repo-review".into(),
};
assert_eq!(i.line, 3);
propertytyperequireddescription
path string yes The invocation surface, repository-relative.
line integer (uint) yes The 1-based line the reference is on.
reference string yes The reference as written.

Used by SkillUsed

SkillProvenance

object
Where a skill came from, when it was not written here first: an opaque marker into a ledger kept outside the repository. The committed half names no source; the ledger entry it points at does, locally.

use majordomus_cli::skill::SkillProvenance;
let p = SkillProvenance {
    origin: "prior-art".into(),
    ledger: "import-2026-09-09#1".into(),
    decision: "adapted".into(),
};
assert!(p.ledger.starts_with("import-"));
propertytyperequireddescription
origin string yes prior-art: the concept was studied elsewhere before this skill was written.
ledger string yes import-<date>#<n>: the entry of the local import ledger that holds the mapping.
decision string yes adapted, reimplemented or merged.

Used by SkillStatus

SkillStanding

"proven" | "partial" | "orphan" | "invalid" | "not_required"
Where a skill stands, derived from the four facts.

use majordomus_cli::skill::SkillStanding;
assert_eq!(SkillStanding::Orphan.label(), "orphan");
  • provenActive, and tested with current evidence, documented, enforced and used.
  • partialActive, named by a test and invoked, with at least one debt a warning names.
  • orphanActive, and no test names it or nothing invokes it.
  • invalidIts file does not satisfy the skill contract; one the index refused outright is listed here too, so a listing never silently shrinks.
  • not_requiredDraft or deprecated: nothing is owed.

Used by SkillStatus

SkillStatus

object
One skill with everything derived about it.

use majordomus_cli::skill::SkillStatus;
// every fact is always present in the answer, so a client never has to tell "absent"
// from "false"; only the provenance is optional
let schema = serde_json::to_value(schemars::schema_for!(SkillStatus)).unwrap();
let required = schema["required"].as_array().unwrap();
for field in ["id", "valid", "tested", "documented", "enforced", "used", "standing"] {
    assert!(required.iter().any(|f| f == field), "{field}");
}
assert!(!required.iter().any(|f| f == "provenance"));
propertytyperequireddescription
id string yes The skill's id, which is its directory name.
uri string yes majordomus://skill/<id>.
title string yes The title as authored.
description string yes The description as authored.
status string yes The lifecycle status as authored: draft, active or deprecated; unknown for a file the index refused.
version integer (uint64) yes The version as authored.
path string yes The file, repository-relative.
provenance SkillProvenance | null → SkillProvenance no Where it came from, when it declares that.
valid boolean yes Whether the file satisfies the skill contract, as the index judged it.
tested SkillTested → SkillTested yes The tested fact.
documented SkillDocumented → SkillDocumented yes The documented fact.
enforced SkillEnforced → SkillEnforced yes The enforced fact.
used SkillUsed → SkillUsed yes The used fact.
standing SkillStanding → SkillStanding yes Where it stands, derived from the four.
findings array of SkillFinding → SkillFinding yes The findings about this skill.

Used by skills.explain SkillStatusList

SkillStatusList

object
Every skill with its derived standing, and how many stand where: what majordomus skills status, GET /api/v1/skills and the majordomus_skills tool answer.

use majordomus_cli::capability::builtin::skills::SkillStatusList;
let empty = SkillStatusList {
    count: 0,
    standings: std::collections::BTreeMap::new(),
    skills: vec![],
};
assert_eq!(empty.count, 0);
propertytyperequireddescription
count integer (uint) yes How many skills.
standings map of integer (uint) yes How many in each standing, keyed by the standing word.
skills array of SkillStatus → SkillStatus yes Every skill, in index order.

Used by skills.status

SkillTest

object
One test that names a skill, and what the ledger says about it.

use majordomus_cli::evidence::ProofState;
use majordomus_cli::skill::SkillTest;
let t = SkillTest {
    path: "test/cases/95_skills.sh".into(),
    test: Some("suite:95_skills".into()),
    state: ProofState::NotRun,
    reproduce: Some("bash test/run.sh 95_skills".into()),
    recorded_at: None,
};
assert_eq!(t.state, ProofState::NotRun);
propertytyperequireddescription
path string yes The test's path.
test string | null no Its identity in the ledger, when a runner owns the path.
state ProofState → ProofState yes What the ledger says about its latest run, for this skill.
reproduce string | null no The command that runs it again.
recorded_at string | null no When the run behind the state was recorded.

Used by SkillTested

SkillTested

object
The tested fact: the tests that name the skill, and their weakest state.

use majordomus_cli::skill::{SkillEvidence, SkillTested};
let t = SkillTested { state: SkillEvidence::Untested, tests: vec![] };
assert!(!t.state.current());
propertytyperequireddescription
state SkillEvidence → SkillEvidence yes The weakest state of the naming tests; untested when there are none.
tests array of SkillTest → SkillTest yes Every test that names the skill, in path order.

Used by SkillStatus

SkillUsed

object
The used fact: every invocation surface that references the skill.

use majordomus_cli::skill::SkillUsed;
let u = SkillUsed { used: false, invocations: vec![] };
assert!(!u.used);
propertytyperequireddescription
used boolean yes At least one invocation surface references the skill.
invocations array of SkillInvocation → SkillInvocation yes Every reference, in path and line order.

Used by SkillStatus

SkillVerification

object
The verdict over every skill: valid when no finding refuses.

use majordomus_cli::capability::builtin::skills::SkillVerification;
let v = SkillVerification { valid: true, skills: 3, failures: 0, warnings: 2, findings: vec![] };
// warnings are debts, reported; they do not make the verdict false
assert!(v.valid);
propertytyperequireddescription
valid boolean yes True when no finding is a failure.
skills integer (uint) yes How many skills were examined.
failures integer (uint) yes How many findings refuse.
warnings integer (uint) yes How many findings are warnings.
findings array of SkillFinding → SkillFinding yes Every finding.

Used by skills.verify

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