The Harness

On this page
  1. Instantiation — a config file at a root
  2. How the dependency tree is walked
    1. Bottom-up, from the bootstrap
    2. Three relations, and they are not interchangeable
    3. The bootstrap is the exception, and the floor rises
  3. What harnessing a directory obliges
    1. coverage.process is DECLARED and is not an obligation
    2. The serialisation is the one obligation a Harness MAY NOT waive
  4. Three things the model asks for and the code does not do
    1. 1. There is no HarnessKind
    2. 2. The stub is NOT free of the definition’s name
    3. A third claim was here, and it was wrong
  5. Where the rules live

This page is generated from docs/source/concepts-harness/ — each section below links to its own source.

✎ Edit

A Harness is the layer that makes a Knowledge Graph usable: it renders the graph, serves an interface over it, and supplies the Skills, Workflows and Tools an Agent works the graph with. The Knowledge Graph says what is true; the Harness is how anybody reaches it.

A checkout holds several. They stack, bottom to top — bootstrap, then cat-harness, then the content core, then folio-assistant — and each inherits what the ones below it declare. That stacking is the subject of this page, along with the two things a Harness owes anything it takes charge of.

The key words MUST, MUST NOT, SHOULD and MAY are used here as RFC 2119 defines them, and only in upper case, per RFC 8174. Where a requirement below is not yet enforced by a gate, this page says so at the requirement rather than in a footnote: a conformance statement a reader cannot tell from a description of existing behaviour is worse than either.

Instantiation — a config file at a root

✎ Edit

An instance becomes an instantiated Harness — as opposed to a dependency that merely sits in the tree — by carrying a config file at its root:

<stub>.config.json      the instance is instantiated HERE, and how it is configured
<stub>.json             what the instance DECLARES — its directories and assets

Two files, two questions, and since 2026-09-21 two filenames rather than two files sharing one shape. Before the split both ended .config.json and were told apart only by which directory they sat in and whether they carried a name — two schemas and two readers behind one spelling, which is a distinction a reader has to already know to make.

The presence of the config is the signal. It is what the left-hand navigation filters on to decide which instances get a tile, so a Harness that declares directories but carries no config is a dependency: its content is inherited by whoever depends on it, and it has no interface of its own. Nothing else is consulted for that question, which is why it could not be derived from the declarations alone.

A Harness MUST own the visualisation published at <baseurl>/<stub>. The stem every published artefact is named with is computed in exactly one place — artefactStub, which returns the declared stub when there is one and the name otherwise — so the site build, the Knowledge Graph export and any future artefact cannot disagree about what an instance is called.

The stub is intended to be free of the definition’s name. A Harness defined as folio-assistant should be instantiable as my-folio-assistant.config.json, with the config pointing at the definition, and <baseurl>/my-folio-assistant then live. That is not what the code does today — see the last section. It is stated here because it is the model the rest of this page assumes, and because a reader who tries it will be stopped by a gate rather than by a schema.

How the dependency tree is walked

✎ Edit

Bottom-up, from the bootstrap

The stack is walked from the lowest instance up, and each layer overlays what the ones beneath it declared. A Skill, a Voice or a directory contributed low is visible everywhere above it; one contributed high is visible only there. Where two layers contribute the same name, the root wins — it is resolved last precisely so that it can.

The order is declared, not computed, in each instance’s needs array:

{ "name": "<content-core>", "needs": ["bootstrap", "cat-harness"] }

It is declared for a measured reason. The layering was real before it was written down, but it lived only in prose, so every consumer that needed the order had to already know it — and the first one that did would have hardcoded four names, which is a rule true only for the instances somebody remembered.

needs is OPTIONAL, and an absent value means undetermined rather than needs nothing. The two are genuinely different: [] is an instance asserting it sits on nothing, which is true of exactly one instance here, while absent is nobody having said. A consumer treating absent as [] would place every unlabelled instance on the floor beside the bootstrap. Names are used rather than paths, because an instance is identified by its name everywhere else and a path breaks the moment a directory moves.

Three relations, and they are not interchangeable

Between repositories — as opposed to between layers in one checkout — the Knowledge Graph page sets out three relations, and a fourth, associated, needs no order at all. Each has its own carrier here:

relationrunscarried by
dependsContent → Contentneeds — the layer stack, foundation first
referencesTool → Content, Test → ContentremoteGraphs — a graph this instance knows about and does not hold
utilizesTool → Tool, Test → Tool, App → Contentdependencies in the config — what this instance USES, overlaid
associatedContent ↔ Content, any harnessassociatedHarnesses — a harness this one knows, and where it is published; never loaded or materialized (issue #1146, skill associate-harness)

Only depends fixes an order, so only depends can have a cycle in it, and a cycle there is a defect rather than a shape. A Tool repository references a Content repository without sitting above or below it; a consumer application utilizes a Content repository it may depend on not at all. That is why a consumer needs all three and collapsing them loses the distinction: “what must be built first”, “what do I know exists”, and “what do I read at run time” are three questions with three answers.

The bootstrap is the exception, and the floor rises

bootstrap sits at the bottom and does not render. It is the navigation footer; what it owes instead is its own .json and .jsonld — “that is its existence”. The rendering obligation is a floor that rises: it starts at cat-harness, which is obliged because it supplies the layers above it with folio/. An instance below that floor declares its exemption in renderExemption rather than being silently excused, so the exemption is a statement somebody made and not a gap somebody left.

What harnessing a directory obliges

✎ Edit

A Harness does not only hold directories. It harnesses them — and a datastore such as a git repository is harnessed the same way, through Tools: the Harness names the Skills that reach the store, and a Tool supplies the mechanism. That indirection is what lets the same Harness be driven against a different store without rewriting a Skill.

Declaring a directory is a promise. It says this instance holds a graph of that kind and a consumer may scan it, and the obligations below are what make the promise keepable. They are stated in full by harness-requirements and measured by check:subgraph-coverage; this section says what they are and why two of them are ranked differently.

obligationdeclared asthe question it answers
serialisationscoverage.serialisationsis each node addressable as json, jsonld and schema.json?
visualisercoverage.visualisercan a person LOOK at this?
docscoverage.docscan a person READ ABOUT this?
skillcoverage.skillis an agent handed something that GOVERNS this?

The first four are per declared directory. A fifth, a starting README, is per instance and is ranked harder still: an instance without one is not a gap somebody has not filled, it is an instance a reader cannot enter.

coverage.process is DECLARED and is not an obligation

A directory may also name the BPMN process that governs it — coverage.process, a diagram’s basename such as document-ingestion. The generated subgraph README then draws that diagram, links its .bpmn source and lists the subprocesses it calls, so the page a reader lands on says how the directory is worked rather than only what is in it.

It sits apart from the four above in two ways.

It is not owed. Most directories are not the subject of a process, so an absent coverage.process means NOT DECLARED and never “no process” — the same third state readOnly keeps — and a directory declaring none gets no such section and is not a finding. What is a finding, ranked major, is a declared name that resolves to no diagram: somebody said, and what they said points at nothing. There is correspondingly no exempt.process; declaring nothing already is the silence, and a waiver would be a second spelling of it.

It is declared rather than inferred, and the temptation is the point. document-ingestion.bpmn starts on an event named “A file lands in uploads/“, so the directory could be matched out of that prose. An event’s name is editorial text: rewording it would silently unlink the page, and nothing could tell that from a directory that never had a process. A diagram also touches several directories while being about one — document-ingestion reads uploads/ and writes library/ — which is the direction name-matching cannot see at all.

The serialisation is the one obligation a Harness MAY NOT waive

A Harness MUST serve json, jsonld and schema.json at a harnessed directory’s own URL and at every node beneath it, and MUST NOT be excused it. coverage.exempt carries three keys — visualiser, docs, skill — and deliberately not a fourth, and the coverage check ranks a missing serialisation as an unmet obligation with no by-kind test at all.

The reason is bootstrap. It is excused a visualiser precisely because its json and jsonld are its existence — so the thing it is excused into cannot itself be excusable, or the exemption would excuse everything. The visualiser is the courtesy; the serialisation is the existence claim.

A Harness SHOULD provide a visualisation, and which kinds owe one is a question about the kind rather than a blanket rule: an active state directory in the repository root — beans/, todos/, fsh-guts/ — owes one because a Harness renders something for each state directory it initialises, while a kind that was never going to need one is not carrying a debt.

That difference is why the coverage axis ranks findings major and minor rather than counting them: major is an obligation this kind owes and the instance has not met; minor is nobody having said yet, for a kind that never owed one. Merging them would rank beans/ having no viewer alongside a kind that never needed one, and an axis that cannot tell those apart is an axis whose count means nothing.

A Harness SHOULD also carry a docs/ directory documenting itself, and every Harness SHOULD have one. cat-harness renders its own through the plain documentation path — no extensions, no JavaScript where it can be avoided — which is why it is the one layer that owns a renderable kind at all: a layer owns the kinds it can render.

Three things the model asks for and the code does not do

✎ Edit

Two parts of the model above are intended and not implemented. They are listed together, with what a reader will actually hit, because a page that described them as working would send somebody to look for code that is not there.

1. There is no HarnessKind

A Harness is intended to be declared as a subtype of a HarnessKind — the name is itself unsettled. Nothing in the schemas registers one today: an instance declares a name, directories, assets and needs, and what makes it a Harness rather than a dependency is the presence of a config file, which is a fact about the filesystem rather than a declared type.

The consequence is not cosmetic. Without a kind there is nowhere to say what a Harness of a given sort MUST supply, so every obligation on this page is prose rather than something a declaration can be validated against.

2. The stub is NOT free of the definition’s name

The model says a Harness defined as folio-assistant MAY be instantiated as my-folio-assistant.config.json, with the config pointing at the definition. Three places refuse it, and they refuse it consistently:

  • instanceConfigFilename composes the filename from the declared name, so the stub is derived rather than chosen.
  • The left-hand navigation tests for <declared name>.config.json when deciding whether an instance is instantiated — a differently-named config would leave the instance with no tile.
  • check:instance-config reports a config whose filename does not match its instance’s name as a finding, reading it as a rename half-done.

And the config carries no field pointing at a definition, so even were the filename freed there is nothing for it to point with. Freeing the stub means adding that pointer first; the filename rule is the symptom.

A third claim was here, and it was wrong

This section listed the JSON-LD serialisation as a MUST carried by nothing. It is carried by coverage.serialisations and checked by check:subgraph-coverage — and it is the strictest obligation in the set, the only one coverage.exempt refuses to waive. The claim was made by reading coverage as three fields when it has four, which is the failure this page warns about one section earlier: treating an overlapping set as the same set.

It is corrected rather than quietly deleted because a reader who saw the first version would go looking for a gap that is not there, and because “stated as a MUST, carried by nothing” is the specific kind of error a page written in RFC 2119 key words is most able to cause.


Neither remaining item blocks the other. Instantiation works, the dependency walk works, and all four coverage obligations are carried and checked. What is missing is the part that would let a Harness be validated as a Harness rather than recognised as one.

Where the rules live

✎ Edit

As with every page here: the discipline is in the Skills, and where a Skill and this page disagree, the Skill wins and this page is wrong.

questionwhere it is answered
What the Knowledge Graph is, and which way its references runThe Knowledge Graph
How an instance declares its directories, and every graph typologydirectory-conventions
What a visualiser owes a declared directorySubgraph viewers
What a Skill states and what a Tool suppliesskills-and-tools
Why a tile belongs to the Harness rather than to a nodeharness-tiles
Context Overlays, and how an Agent’s conditions are setManaging agent context

The declaration schema, including needs, remoteGraphs, renderExemption and coverage, is schemas/cat-harness.ts; the config schema, including dependencies, is schemas/harness-config.ts.


folio-assistant — code under the Apache License 2.0, documentation under CC BY 3.0. Generated schema and API references are rebuilt from source on every push to main.

deployed b08c961 · 2026-10-23T23:11:09Z