Skip to content

system prompt

Mike Crowe edited this page Jul 3, 2026 · 3 revisions

Configuring an agent's identity and rules across harnesses

Status: proposed, not yet implemented. This guide documents the target design from docs/todos/2026-07-02-cross-harness-identity-and-rules.md — written before the code, as a way to sanity-check the design by describing it the way a user would experience it. Nothing below works today. See that spec for the research this is based on, and docs/research/harness-rules-directory-support.md for the per-harness native-support findings.

Why this exists: containerize configuration, not storage

Everything in this guide serves one rule: your conversations, usage, and stats must persist and stay coherent no matter which stack (tool configuration) you ran — switching from claude_dev-core to claude_dev-methodology shouldn't fragment your Claude history into two silos, because both mount the same host history, keyed by project path, not by stack name. The same idea makes team-shared configurations possible: a stack (its recipes, rules, and the instructions: file this guide introduces) is the shareable, version-controlled unit a team standardizes on — your auth and history stay strictly personal and host-linked, never baked into that shared config. instructions:/rules: isolate what tools and identity this stack gives the agent; they never touch where your conversations live.

If you're authoring a stack, watch for state.session_state: volume. It's a valid sibling of the default host in the schema, but it does the opposite of everything above — it isolates session state per-stack instead of sharing your host history. Fine if you deliberately want a throwaway, non-continuous stack; a real footgun if it ends up in a team-shared config by accident (e.g. copy-pasted from an example). See the stacks guide for the full field.

The two-tier model

Two different things get configured here, owned by two different authors:

  • Identity — "what is this assembled agent, as a whole." One per stack, written once by whoever builds the stack. This is CLAUDE.md's role today; the goal is the same thing for every harness.
  • Rules — topic-scoped, composable instructions. Contributed by whichever recipes are in the stack, N per stack. Already shipped as the rules: recipe field — this guide extends it to more harnesses, it doesn't replace it.
flowchart TD
    subgraph recipes["Recipes in the stack"]
        R1["beads recipe<br>rules/bd-usage.md"]
        R2["superpowers recipe<br>rules/workflow.md"]
    end
    subgraph stack["Stack author"]
        SI["stack.yaml<br>instructions: identity.md"]
    end

    R1 --> Compose["Composed rules<br>.claude/rules/*.md"]
    R2 --> Compose
    SI --> Identity["Stack identity<br>.claude/CLAUDE.md"]

    Compose --> Build["harnessed build"]
    Identity --> Build

    Build --> Derived["Per-harness derived files<br>(one path per target harness)"]

    classDef recipe fill:#dfe6e9,stroke:#636e72
    classDef stack fill:#d4edda,stroke:#28a745
    class R1,R2,Compose recipe
    class SI,Identity stack
Loading

Trying to solve both with one mechanism is what caused the original design confusion — "just append everything to CLAUDE.md" breaks down the moment two recipes want to append to it. Rules compose (fail-fast on name collision); identity doesn't — it's one file, one owner.

Declaring stack-level identity

A new instructions: field on the stack manifest, pointing at one markdown file relative to the stack's own directory:

# catalog/stacks/claude_superpowers/stack.yaml
name: claude_superpowers
harness: claude
recipes: [superpowers]
instructions: identity.md
<!-- catalog/stacks/claude_superpowers/identity.md -->
You are a focused pair-programmer for this repo. Prefer small, reviewable diffs. Always run the
test suite before declaring a task done. When in doubt about scope, ask rather than guess.

This is deliberately a single file, not a list — identity isn't composable the way rules are. If you need different identities for different harnesses of the "same" stack, author separate stack manifests (claude_myproject, omp_myproject, ...) each with their own instructions:.

Declaring recipe-level rules

Unchanged in shape from today's rules: field — a recipe ships flat .md files, each one a composable topic:

# catalog/recipes/beads/recipe.yaml
rules:
  - {path: rules/bd-usage.md}

(This guide also covers the fix to how these fan out — see Known limitations below for the flat-file correction.)

Per-harness resolution

Not every harness has a literal "modify the system prompt" primitive. Where one exists that adds to the default behavior, it's used directly. Where the only option replaces the harness's built-in behavior wholesale, that's treated as an opt-in escape hatch, not the default — the same caution a stack author should apply themselves before reaching for it.

flowchart LR
    Start{"Does the harness have a<br>system-prompt hook?"}
    Start -->|"No hook exists"| Fallback["Append into the harness's<br>own global instructions file"]
    Start -->|"Yes — additive"| Additive["Use it directly"]
    Start -->|"Yes — but full-replace"| Replace["Opt-in escape hatch only,<br>not the default target"]

    Fallback --> Claude["claude → .claude/CLAUDE.md<br>(the only surface it has)"]
    Fallback --> CodexD["codex → ~/.codex/AGENTS.md<br>(default)"]
    Additive --> Omp["omp → ~/.omp/agent/APPEND_SYSTEM.md<br>(guarded, shared host file)"]
    Replace --> CodexR["codex → model_instructions_file<br>(only if explicitly requested)"]

    Start -->|"Requires a launch-time change"| OC["opencode → custom agent + --agent flag"]
    Start -->|"Bake alongside existing MCP config"| AG["antigravity → gemini.md / settings.json"]

    classDef additive fill:#d4edda,stroke:#28a745
    classDef caution fill:#fff3cd,stroke:#ffc107
    classDef fallback fill:#dfe6e9,stroke:#636e72
    class Additive,Omp additive
    class Replace,CodexR caution
    class Fallback,Claude,CodexD,OC,AG fallback
Loading
Harness Identity target Rules target Notes
claude .claude/CLAUDE.md .claude/rules/*.md (native, conditional via paths:) The reference implementation — everything else approximates this
omp ~/.omp/agent/APPEND_SYSTEM.md (guarded append) ~/.omp/agent/RULES.md (guarded append) Shared across every omp stack on this host, not stack-scoped — see limitations
codex ~/.codex/AGENTS.md (default) Same file, concatenated in model_instructions_file available but not default (full-replace)
opencode Custom agent in opencode.json, launched via --agent <name> instructions: glob at an absolute container path Confirmed safe — a custom agent with only a prompt field inherits default permissions
antigravity gemini.md / settings.json's context.fileName .agents/rules/*.md (native, flat files) Baked alongside the existing mcp_config.json, same mechanism

Build-time flow

Everything except omp's guarded append happens at harnessed build — generated once, written into the stack's profile, no container involved yet:

sequenceDiagram
    participant Author as Stack author
    participant CLI as harnessed build
    participant Assembler
    participant Profile as Profile dir (host, per-stack)

    Author->>CLI: harnessed build my-stack
    CLI->>Assembler: load stack.yaml + every recipe
    Assembler->>Assembler: fan recipe rules -> .claude/rules/*.md
    Assembler->>Assembler: render stack instructions -> .claude/CLAUDE.md

    alt harness is codex
        Assembler->>Profile: write ~/.codex/AGENTS.md (identity + concatenated rules)
    else harness is opencode
        Assembler->>Profile: merge a custom agent into opencode.json
    else harness is antigravity
        Assembler->>Profile: bake settings.json + a flat identity.md
    end

    Assembler->>Profile: write the rest of the profile tree
    CLI-->>Author: build complete
Loading

Launch-time flow — the omp exception

omp's target files (~/.omp/agent/{APPEND_SYSTEM,RULES}.md) aren't profile-scoped — they're the real host directory, shared with your native host omp and every other omp-harness stack (_omp_agent_mount). Writing them can't happen at build time into a per-stack profile; it happens at launch, directly on the host, guarded so repeated launches don't duplicate content:

sequenceDiagram
    participant User
    participant CLI as harnessed <stack>
    participant Host as Host filesystem (~/.omp/agent/)
    participant Container

    User->>CLI: harnessed my-omp-stack
    alt harness is omp
        CLI->>Host: check for this stack's delimiter block
        alt block already present
            CLI->>Host: no-op
        else block missing or stale
            CLI->>Host: write/replace the block, markers intact
        end
        Note right of Host: <!-- harnessed:stack:my-omp-stack --><br/>...content...<br/><!-- /harnessed -->
    end
    CLI->>Container: mount the profile's .claude/, compose the pod
    Container-->>User: attach
Loading

Known limitations

These are accepted tradeoffs, not bugs to file later:

  • No conditional rule loading outside Claude. Claude Code rules can carry paths: frontmatter to load only when a matching file enters context. Every other harness gets the concatenated rules always-on — a rule scoped to *.py files becomes globally active for codex/omp/etc. This is a deliberate, permanent limitation.
  • omp's identity/rules are host-shared, not per-stack. Multiple omp-harness stacks (and your native host omp) all read the same ~/.omp/agent/{APPEND_SYSTEM,RULES}.md. The guarded-append markers keep different stacks' blocks from clobbering each other, but the content still lives in one shared file — there's no container-level isolation the way .claude/CLAUDE.md has.
  • Codex has a 32 KiB instruction budget (project_doc_max_bytes), shared between identity and concatenated rules if both land in AGENTS.md. A stack with a long identity file and many recipe-contributed rules can silently truncate — files closer to the cap get cut, not warned about.
  • The Claude Code rules: fan-out had a real bug this design also fixes: rules are flat .md files (.claude/rules/<name>.md), not one directory per rule — the original rules: implementation copy-pasted the skills: directory-fan pattern without checking the shape. Fixed as part of landing this design, independent of everything else here.

Worked example

Starting from the existing claude_superpowers stack, adding an identity file and a second harness:

# catalog/stacks/claude_superpowers/stack.yaml — unchanged shape, one new field
name: claude_superpowers
harness: claude
recipes: [superpowers]
instructions: identity.md
# catalog/stacks/omp_superpowers/stack.yaml — same identity content, different harness
name: omp_superpowers
harness: omp
recipes: [superpowers]
instructions: identity.md

Building both:

harnessed build claude_superpowers   # writes .claude/CLAUDE.md into this stack's profile
harnessed build omp_superpowers      # writes .claude/CLAUDE.md into this stack's profile too
harnessed omp_superpowers            # LAUNCH TIME: guarded-appends identity.md's content into
                                      # ~/.omp/agent/APPEND_SYSTEM.md, then attaches

Note the asymmetry: claude_superpowers's identity is fully self-contained in its own profile. omp_superpowers's identity only actually reaches the agent at launch, and lands in a file shared with every other omp stack you run — that's the tradeoff called out above, made concrete.

See also

Clone this wiki locally