Model Citizen by Jake Selby v0.13.1
GitHub
Doc

Preferences

Path
docs/preferences.md
Lines
304

Two kinds of preference exist, and they are configured differently because Claude Code reads rule text literally: there is no variable substitution inside a rule or CLAUDE.md.

Nine stance axes ship, and only three of them bind to enforcement today: autonomy sets which shell-command grade stops and asks, delegation changes how a spawn is routed, and cost resolves a class, an effort and a soft budget per role. The other six are prose that swaps cleanly and acquires no enforced control by being switched. harness usage --rules --by stance groups rule hits by the variant in force, so a switch can be checked rather than assumed.

Identity

The identity block of ~/.config/agent-harness/config.json (name, pronouns, role, github, timezone) is rendered into ~/.claude/CLAUDE.personal.md on every sync. Anything you write below the <!-- harness:personal-below --> marker in that file survives a re-render; that is also where a personal writing-voice profile belongs. Tracked rules never carry a name; they are written in second person.

Env overrides: HARNESS_IDENTITY_NAME, HARNESS_IDENTITY_PRONOUNS, and so on.

expertise is expert (the default, and what every config that predates the field resolves to) or beginner. It selects one paragraph of the personal file: expert skips fundamentals, beginner says what each step and command does, defines terms on first use, and assumes no prior knowledge of programming, version control or the command line. It is the only identity field that changes behaviour rather than describing you.

harness init writes the whole file by asking, and harness config set identity.name "…" changes one field. Neither needs an editor. Until name, role and github differ from the example file, sync and doctor both say so: what they hold is what the agent believes about you, so a config left unedited has it addressing you by the placeholder. pronouns and timezone are never reported, because they/them and UTC are answers someone might mean.

Stances

Each stance is a directory of variants under primitives/stances/; config picks one and sync projects it into the selected runtimes. Claude receives a link under ~/.claude/rules/harness-stances/<stance>.md; Codex receives the same resolved source in its generated instructions. An off variant remains explicit rather than silently removing the dimension.

StanceVariantsDefault
licensingpermissive-commercial, open-source, offpermissive-commercial
build-vs-buycapability-ceiling, offcapability-ceiling
commitsconventional-attributed, conventional, as-you-go, offconventional-attributed
plan-ceremonyreview-card, lightreview-card
delegationtiered, session-model, offtiered
testingrequired, pragmatic, offrequired
autonomyexecute, confirm-writes, askexecute
costfrugal, balanced, maxbalanced
voicescannable, answer-card, offscannable

harness config set stances.testing off checks the variant exists before writing, and names the options when it does not.

Presets

The defaults above are a professional software workflow, and escaping it meant finding five separate opt-outs. harness init asks what the work is and uses a preset as the defaults for the questions that follow; every stance is still asked, so a preset is a starting point, not a lock.

PresetChanges from the defaults
softwarenothing; the table above
generallicensing: off, build-vs-buy: off, commits: off, testing: off, plan-ceremony: light

general is for work that is not shipping software — writing, research, organising files, a personal script. It leaves delegation, autonomy and cost alone, because how work is spread, how far it runs unattended and what it costs are the same questions whatever the work is.

The topic rules are not switchable and do not have presets; a rule about repositories, tests or pull requests simply does not apply when the task is not code work, and the always-loaded preamble says so.

Env overrides win over the file: HARNESS_STANCE_LICENSING=open-source, HARNESS_STANCE_COMMITS=off. At sync time the env value is what gets linked. At session start the harness-session.py hook compares the environment with the synced config and injects one line of context for any difference, so HARNESS_STANCE_TESTING=off claude works for one session without a re-sync.

The plan-ceremony stance also decides whether the plan-card validator runs. Registration is unconditional — one coordinator per lifecycle event, as in how it works — so the stance is read inside dispatch, at the moment a plan file is written, and a switch takes effect in the next turn rather than at the next sync.

What a session costs

Every turn spends tokens against your plan’s limit, and the ones that fan work out to several subagents spend several times as much: /research and /build are the expensive commands, and a wide review is the expensive habit.

The cost stance is the dial. It sets how much; the delegation stance sets what gets delegated and to which model tier.

A variant controls more than the session’s own habits. Its switches set the session’s reasoning effort, the fan-out width, whether fast mode and compaction are available, and how much the usage feed says about spend; its rows set a model class, a reasoning effort and a soft budget in output tokens and tool calls for each role and for each of the A, B and C bands, and name the band an unnamed spawn is routed to. All of it is data in a JSON sidecar beside the variant’s .md, and harness stances --json prints the resolved table with the sidecar each layer came from.

  • frugal runs the session at low effort, keeps the fan-out narrow, never turns fast mode on, ends a task with /clear, drops the cheaper bands a class each, and scales every budget down.
  • balanced is the shipped default: medium effort, a moderate fan-out, fast mode off unless you ask for it, /clear at task end, and the measured budgets unscaled.
  • max leaves effort at the model’s default, fans out as widely as the task needs, allows fast mode and compaction, and marks nothing as over budget.

Select one with harness config set stances.cost frugal, or for a single session with HARNESS_STANCE_COST=frugal claude. To write your own, put a .md and a sidecar in your primitive root, extends a shipped variant and change only the cells you care about; primitive-authoring.md has the worked example and the schema.

Two things a variant never wins against. A role_bindings.<runtime>.<role> entry in your config always beats the row, because you named the role yourself. And a role whose contract says posture: fixed — the verifiers — ignores a variant’s class and effort entirely and takes only its budgets.

A row’s budget reaches the work as one sentence brief-guard appends to a brief that states no spend of its own. It is soft — finish if close, otherwise return — and a variant that prices nothing changes no brief. The shipped per-role budgets are the 90-day p75 from harness usage --by role; the A/B/C band budgets are provisional — the general-purpose distribution at p50, p75 and p90 — until rerouted spawns have measured each band, and the bands themselves are a first cut to be re-seeded the same way.

harness usage summarises what sessions have actually spent, from a local file with no network call unless you opt into exporting it — see usage.md. It reports dollars as well as tokens, from policy/prices.json. A prices block in config.json merges over that file per model id, so you can correct a rate your account is billed differently at, or add a model the table does not list:

{ "prices": { "claude-opus-5": { "input": 4.0 }, "some-local-model": {
    "input": 0.0, "output": 0.0, "cache_read": 0.0, "cache_write": 0.0 } } }

An override that names one rate keeps the rest of the shipped entry; a new model needs all four.

Other surfaces

"claude": { "manage": true }, "codex": { "manage": true } and "vscode": { "manage": true } independently select the runtime and editor surfaces that sync may manage. Set any of them to false to leave that surface alone; neither Claude nor Codex requires the other.

Permission posture

permissions in config is inherit (default: the harness never touches permission mode), bypass, auto or manual. When set, one knob drives permissions.defaultMode in Claude Code, claudeCode.initialPermissionMode and claudeCode.allowDangerouslySkipPermissions in VS Code, and approval_policy plus sandbox_mode in Codex. Session environment values do not grant native permissions or mutate global configuration.

bypass is refused unless permissions_bypass_acknowledged is true in the config file (the env override cannot grant it). It turns off every permission prompt and puts Codex in danger-full-access with no sandbox. It is a posture for an isolated personal machine. Never select it on a machine that touches regulated or customer data, and never carry it into an organisation’s fork of this harness: leave inherit and let the organisation’s managed settings decide. auto is the right choice for a supervised but low-friction setup.

What plan mode may do at that posture

Plan mode exists to force a plan, its questions and a wait before anything is built. It is not a reason to investigate at a lower authority than you selected for every other mode, and natively it is: the allow rules and the read-only hook cover the commands the grammar can prove, so a script run, a python3 -c, a redirect into a scratch file or a test run still prompts.

So under bypass or auto, in Claude Code, while permission_mode is plan, the PreToolUse coordinator answers what the native flow would prompt on:

  • Graded 0 or 1 — proved read-only, or writing only to this machine — is approved as investigation.
  • Graded 2 — a push, a release, an API call that mutates, anything a colleague would see — is asked about, because that is execution rather than planning. Graded 3 is unchanged.
  • manual and inherit keep today’s behaviour, and Codex is untouched: its client rejects an allow decision outright.
  • The autonomy stance still wins where it is stricter. confirm-writes or ask asks about the same grades in plan mode that it asks about everywhere else.

plan_allow_tools is a list of tool-name globs (fnmatch syntax, for example "mcp__notes__read_*") approved in plan mode under the same posture gate. It is empty by default and nothing is inferred: a PreToolUse payload says nothing about whether an MCP tool reads or writes, so only you can say which of them are research. Entries that are not non-empty strings are ignored, and a glob never reopens a tool the coordinator already governs — Bash keeps its grades, Agent its delegation guard, WebFetch its own plan-mode hook. Set it with harness config set plan_allow_tools '["mcp__notes__read_*"]'.

The reasoning behind each stance

Stance files carry the preference and its operative bullets only, because they count against the always-loaded cap. The arguments live here.

Licensing. permissive-commercial exists to catch the case that does not look like a licensing question, which is why the trigger is “before incorporating or upgrading any third-party material” rather than “when a licence looks unclear”. Preferred software licences: MIT, BSD-2-Clause, BSD-3-Clause, ISC, Apache-2.0, 0BSD; preferred content licences: CC0-1.0, CC-BY-4.0 — candidates, not a substitute for checking the exact version and any bundled material. Excluded without exception: GPL, AGPL, LGPL, MPL, CC BY-SA and ODbL, plus noncommercial, no-derivatives, editorial-only, research-only, field-of-use, advertising-credit, time-limited, revocable-at-will and paid proprietary asset licences. Standalone development tools may use copyleft licences where merely using them imposes no obligation on your output; check bundled runtime components separately, and never read that exception as permission to incorporate copyleft code or assets. “Free download”, “royalty-free”, “source available” and a marketplace tag are not proof: an uploader’s licence claim alone does not establish ownership, and no game rips, unlicensed copies or material with suspect rights qualify. Under open-source, copyleft qualifies when the project’s own licence is compatible and the obligations are recorded in the manifest.

Testing. Under required, “build feature X” always means build it, make every existing test pass, and write tests covering every new capability: tests are part of the definition of done, not a separate step. There is no circumstance where shipping code without tests is acceptable — not for speed, not for “simple” changes, not for “I’ll add them later”, because later never comes. pragmatic asks instead that a test buy something, and that the report say plainly which new behaviour is untested and why.

Commits. type(scope): summary, with feat, fix, docs, chore, refactor, test and ci as the usual types. The quality gate runs on HEAD in the exact checkout you are about to push, because a gate run somewhere else proves nothing about what lands. Attribution trailers are how a reader knows an agent wrote the change, which is why conventional-attributed keeps them and conventional drops them. as-you-go exists for repos where stale local-only state is pure cost — planning repos, knowledge bases, dotfiles — and explicitly does not apply to application repos with a review gate.

Autonomy. Set defaults by reversibility and blast radius: a deploy is never autonomous, a local edit always is. The anti-patterns execute rules out: ending with “run this in your terminal:” for setup you can perform, pasting install or start instructions instead of running them, and asking “say the word and I’ll…” for work you can do in the same turn. An autonomy level granted for one scope does not extend to the next, so never widen your own permissions or record a governance rule for yourself unless the user asks. The grade-bash hook enforces the stance: it grades every shell command 0–3 and gates at grade 3 under execute, 2 and up under confirm-writes, 1 and up under ask; grade-bash.py documents the grades and the modes.

Plan ceremony. review-card makes plan mode the review surface: /plan asks to enter it, writes the card into the file plan mode designates, posts the approach in chat and finishes at ExitPlanMode, so the pane renders the plan and the native approval is the gate. Approval is also when the naming happens: /plan renames the runtime-generated file to a topic slug and hands /build that path — which is why /build never searches for a plan, and why the builder is what commits the file, into the worktree the pull request comes from. Where there is no plan mode — Codex, and any plan written outside /plan — the file is named for the topic, opened for the reviewer and closed with the typed build line instead. Either way, autonomous implementation follows approval. The card is a review document before it is an execution document — length below it is free, length above it is the defect — and a hook validates it on every write, whatever the runtime named the file. Skip the ceremony only if the user explicitly asks for a quick plan or says to just exit plan mode. light drops the file and the validator but keeps the explicit go-ahead.

Build versus buy. The user has heard the maintenance-burden argument and rejects its premise: code is cheap now, and an agent-assisted person can maintain custom code fine in three years. Capability ceilings are the argument class that decides.

Cost. cost governs how much you spend, never which model: a row names a capability class, the adapter’s table resolves it, and agent definitions carry the result. Under frugal subagents are gatherers only and agent teams are off, so an up-class trigger is answered by raising the session’s own effort rather than by spawning. What each variant sets is above, under what a session costs; how it meets the tier decision is in the delegation-tiering skill.

Voice. voice governs how a reply is laid out, and nothing about what the work is. scannable defers to the Scannable output style: verdict first, registers separated, at most one table. It is the only variant that carries presentation material, on either runtime; under answer-card and off a sync installs no output style and takes back out the one a previous selection left, while an output style you chose yourself is left exactly as it is, whatever it is called. answer-card is for reading on a phone — the answer in the first line, then why, the catch, and the alternatives, about 150 words, no tables, with the reasoning left in the file it links rather than re-argued in the message. It wins over the output style where the two differ. off imposes no shape at all. A personal voice profile is different: it tells the agent how to draft in the user’s name and stays in the untracked personal file. The rule keeps only what no variant changes: a subagent inherits no voice, so its brief has to carry the output shape itself.

Delegation. The evidence for the tier bands, the cost-per-solved-task numbers and the boundaries where they stop holding are in the delegation-tiering skill, not here. The tier-agent-spawns hook enforces the chosen variant on spawns that name no agent definition: routed to the cost variant’s default band worker under tiered, untouched under session-model, a prompt under off.

Band workers. worker-a, worker-b and worker-c are the three roles the A/B/C bands render into, and they exist for one reason: the Agent tool takes no effort, so only an agent definition can carry the posture’s effort to a spawn that named nothing. Such a spawn is rewritten to the variant’s default_band worker — B under balanced, A under frugal — and an orchestrator that wants another band spawns that worker by name; their descriptions carry the band rule the delegation-tiering skill argues. A variant with no default_band routes nothing; so does a machine that has not synced the definitions, and so does a session that started before it did, which is why a sync that installs them wants a new session after it.

Proposing a new stance or variant

A stance is right when a competent engineer could reasonably want the opposite. Add the variants under primitives/stances/<name>/, add the name to STANCE_NAMES in bin/harness, add the default to config.example.json, add a row here, and add a line to the CHANGELOG.

What is deliberately not a stance

The always-loaded rules in primitives/rules/ do not switch. A rule has to hold whichever way every stance is thrown, which is what lets the harness install for someone whose preferences nobody knows. Apply the same test in reverse before adding one: if a competent engineer could reasonably want the opposite, it belongs in primitives/stances/, not primitives/rules/.

Two rules do not pass that test yet, tracked rather than hidden. voice-and-format.md hard-wires the Scannable output style (#68), and conciseness.md is comment and doc style. cache-hygiene.md is cost-dimension content the cost stance already points at.

Extend your choices

Stances are custom harness primitives, not native provider features. Add dimensions, variants and constraints through the authoring contract. Inspect effective selections and adapter coverage with harness stances --json; native restrictions remain authoritative.

↑↓ move↵ openPagefind · indexed at build