Model Citizen by Jake Selby v0.13.1
GitHub
View source
Doc

Your primitives, your working style

Path
docs/primitive-authoring.md
Lines
159

Personal stances are harness-defined switches for how your agents work. They are not a native Claude Code or Codex feature. One selected variant supplies policy to both runtime adapters; native restrictions still apply. Guidance is not proof that a runtime enforces a preference.

Select and inspect a stance

bin/harness config set stances.voice answer-card
bin/harness stances
bin/harness sync --dry-run

After reviewing the changes, bin/harness sync installs the selection. Claude Code reads the selected stance through its linked rules; Codex reads the same text in generated instructions. Use HARNESS_STANCE_VOICE=scannable bin/harness stances --json to inspect a session selection. Setting an environment variable on a running agent does not itself rewrite its loaded context.

Resolution order is distribution defaults, user configuration, the optional file explicitly named by HARNESS_PROJECT_CONFIG, then HARNESS_STANCE_* session values. Project files can select stances only. They cannot change identity, runtime targets or permission configuration.

Define a personal stance

Create a directory outside the checkout, such as ~/.config/agent-harness/primitives, and add its absolute expanded path to the primitive_roots array in your user configuration. Create stances/feedback/direct.md there with this content:

# Feedback stance: direct

Lead with the conclusion. Name the specific evidence and the next useful action.

Add stances/feedback/gentle.md with your alternative policy, then select it with bin/harness config set stances.feedback gentle. bin/harness stances --json shows its source and full resolved behavior. Custom dimensions are optional until selected. Identifiers use lowercase letters, digits and hyphens. Duplicate dimension/variant definitions, unknown selections and path traversal are errors, not fallback behavior.

A skill, role or workflow name defined in two roots is an error as well. harness lint names both sources and harness sync refuses before it writes anything, because a runtime resolves a duplicate name silently, first-wins. A project’s own .claude/agents/ or .claude/skills/ name is not a duplicate: the project definition is meant to win, so sync reports the shadow as a notice and carries on.

Optional constraints.json alongside stances/ can reject incompatible choices:

{"stances": [{"when": {"feedback": "direct"}, "excludes": {"voice": "off"},
  "reason": "Direct feedback requires an active voice policy"}]}

Each rule has a nonempty when selection, a one-sentence reason, and at least one of requires, excludes and excludes_roles. All when entries must match to activate the rule; every requirement must match and no excluded choice may be selected. Any other field is an authoring error rather than a key a later version might read.

excludes_roles is the one condition that reads something other than the selection: the frontmatter of the shipped role contracts, with allow naming the roles a skill exempts by name. It is how the harness states in data that delegation: tiered refuses the frontier class while designer and design-judge are allowed to declare it:

{"stances": [{"when": {"delegation": "tiered"},
  "excludes_roles": {"tier": "frontier", "allow": ["designer", "design-judge"]},
  "reason": "Only the design roles the delegation-tiering skill exempts may declare frontier"}]}

A violated constraint is a finding in harness stances --json (a conflicts array) and in harness lint, which evaluates the shipped constraints against config.example.json. It stays a hard error in the resolver, so harness sync refuses the selection rather than projecting a contradiction; validation runs before sync changes files.

A cost variant with numbers in it

A cost variant is prose in <variant>.md and, optionally, data in <variant>.json beside it. The sidecar is what the harness resolves; the prose is what your agent reads. In your primitive root — ~/.config/agent-harness/primitives, say — write stances/cost/careful.md with your policy, then stances/cost/careful.json:

{"schema_version": 1, "extends": "balanced",
 "switches": {"session_effort": "high", "budget_multiplier": 1.4},
 "rows": {"gatherer": {"effort": "medium"}}}

extends names another cost variant and may chain up to five deep; cycles stop resolution with a warning. Each layer is merged over the one it extends, switch by switch and row cell by row cell, so the example above changes three values and inherits every other one. A link that cannot be followed — no sidecar, unreadable JSON, a schema_version the installed release does not read, a name that is not a primitive identifier — resolves to balanced’s table with a warning, so a variant is never silently empty.

default_band names the band an unnamed spawn is routed to, and omitting it everywhere on the chain routes nothing at all.

A row is keyed by a role name or by a band — A, B or C — and may set class, effort, budget_output_tokens and budget_tool_calls; any of them may be omitted, and a null budget means unbudgeted. budget_multiplier scales both budgets, and harness stances --json reports the base figure and the scaled one. class never names the top class: reaching it by request is exactly what the delegation stance forbids, and it only applies at all when that stance resolves to tiered. A role whose frontmatter says posture: fixed — the verifiers — keeps its own class and effort whatever a row says, and takes the row’s budgets.

Unknown keys are ignored with a warning rather than an error, so a switch added in a later release never breaks a variant you wrote. Run bin/harness stances --json to see the resolved table, its extends_chain, each sidecar’s path, and any warnings.

Import instructions you already have

bin/harness import path/to/CLAUDE.md turns an existing instruction file into rules under an external primitive root, so adopting the harness does not mean discarding what a repository already tells its agents. It reads CLAUDE.md, AGENTS.md, .cursorrules and .cursor/rules/*.mdc; rulesync’s import is the reference for the behaviour.

Each top-level ## section becomes rules/<slug>.md carrying the source path, the import date and the original heading as front matter. The prose above the first section becomes rules/<name>-preamble.md, a @-import in a CLAUDE.md is followed one level and imported as its own rule with its own source:, and a .mdc file’s description, globs and alwaysApply are carried through unchanged. A ## inside a fenced block is text, not a heading. Nothing is dropped: a heading that yields no identifier, a second section claiming a name already taken and a front-matter line that is not a field all land in one rules/<name>-unsorted.md with a note saying so.

bin/harness import ~/code/project/CLAUDE.md --dry-run   # the plan, then the sync projection
bin/harness import ~/code/project/CLAUDE.md             # writes exactly that plan

The first run only prints — the rules it would write, then harness sync --dry-run for the root that would carry them — and a second run applies the plan it printed; a source that changed in between is printed again rather than written. Rules land under ~/.config/agent-harness/imported/<name>/ unless --root names another absolute directory, never under this repository’s primitives/, and the root is added to primitive_roots only once the files exist. A root that carries no stances/ is fine. Importing a file the ownership journal says the harness generated is refused with its record, and so is a skill, role or workflow name the new root would define twice, in sync’s own words and before anything is written.

Once the root is registered, harness sync projects it like any other: the imported rules are linked into ~/.claude/rules/harness-roots/<root>/ and rendered into the Codex AGENTS.md after this repository’s own rules, and any skills the root carries are linked beside the shared ones. The sync model covers the ordering, the drift reporting and what uninstall takes back.

Contribute shared primitives

Author rules, stances, skills, roles, workflows and presentation under primitives/. Role instructions and authority are shared; native model/tool settings belong in adapters/<runtime>/bindings.json. Workflow bodies use {{arguments}}; the Claude command projection translates that to its native argument syntax. Existing claude/ source paths are compatibility links or generated views, not another authoring home.

Run bin/harness generate after editing roles, workflows or base instruction templates. bin/harness generate --check and lint reject projection drift. bin/harness catalog emits stable kind/ID/source/digest records for documentation and integration readers. Installation coverage and runtime enforcement are separate from successful source generation.

↑↓ move↵ openPagefind · indexed at build