Skip to content

cross harness identity and rules

Mike Crowe edited this page Jul 27, 2026 · 2 revisions

Cross-harness identity (system prompt) and rules concatenation

Status: design spec — not yet implemented. The 6 phases below are tracked as bd issues (main-3va flat-file rules fix, main-ylz stack-level instructions:, main-72j antigravity, main-7rh codex, main-rlw opencode, main-w8k omp) — this doc lives in docs/research/ as the implementation reference until the last phase ships, then the governing-principle section and harness-comparison table below should move to docs/harnessed-design.md. Date: 2026-07-02 (governing principle + omp correction added 2026-07-03) Companions: harness-rules-directory-support.md (per-harness native .agents/rules/-equivalent research), docs/guides/recipe-authoring.md (the rules: field), src/harnessed/synclinks.py (current fan-out mechanism), home-folder-codex-requirements.md and home-folder-opencode-requirements.md (2026-07-03 — the history-surfacing research this spec's "don't confuse config with storage" principle depends on for two more harnesses).

Governing principle (established 2026-07-03)

Containerize the configuration, not the storage. Conversations, usage, and stats must persist and stay coherent regardless of which harnessed stack (tool configuration) launched a given session — the same underlying identity/history for a harness should be usable across multiple different stacks (e.g. claude_dev-core and claude_dev-methodology both writing into the same host Claude history, because the mount is keyed by project path, not by stack name). This is also the foundation for team-deployable configurations: a stack (recipes + rules + the instructions: field this spec adds) is the shareable, version-controlled unit; auth and history stay strictly personal and host-linked, never baked into shared config.

This reframes one earlier conclusion in this spec: omp's full ~/.omp/agent rw-mount (see below) is not a risk to trim — it's the target pattern, because it's what guarantees continuity across every omp-harness stack and native host omp. See ROADMAP.md D3 (now closed, confirmed correct) and docs/research/home-folder-omp-requirements.md (updated 2026-07-03 to reconcile its original, now-superseded recommendation against this).

Concrete landmine to guard against: Stack.state.session_state already supports volume as an alternative to the default host (see docs/guides/stacks.md) — that isolates session state per-stack instead of sharing the host store, the exact opposite of this principle. A team-shared stack that sets this (even accidentally) would silently fragment everyone's history per-stack. Worth a loud warning wherever session_state is documented, and possibly worth requiring an explicit, harder-to-stumble-into opt-in for volume rather than a same-looking sibling of host.

Problem

Two genuinely different concerns were getting conflated in earlier discussion:

  1. Stack-level identity — "what is this assembled agent, as a whole." One per stack, authored once by the stack author. This is CLAUDE.md's role for claude, and the closest thing to a system prompt override for the others.
  2. Recipe-level rules — topic-scoped, composable, N-per-stack, already shipped as the rules: recipe field (T10). A beads recipe ships a rule about bd usage; a superpowers recipe ships one about its workflow; they compose without collision (synclinks.py fail-fast on name clash).

Trying to solve both with one mechanism is what caused the earlier confusion (e.g. "just append to CLAUDE.md" breaks down the moment two recipes want to append). They need separate schema fields and separate build-time handling.

Also: T10's rules: fan-out has a real bug, independent of everything below. Official Claude Code docs confirm rules are flat .md files (.claude/rules/<name>.md, or nested for topic organization — .claude/rules/frontend/react.md — but never a directory-per-rule). synclinks.py _register() currently requires src.is_dir() and does shutil.copytree — the skills: pattern, copy-pasted for rules: without checking the shape. Skills genuinely need directories (SKILL.md + resources); rules don't. Fix this regardless of the design below — it's a small, isolated correction (FileExt needs a file-vs-dir mode, or a new leaner RuleExt).

The two-tier model

Tier Owner Cardinality Claude Code analog
Identity Stack (new field, e.g. instructions:) One per stack .claude/CLAUDE.md
Rules Recipe (existing rules: field, T10) N per stack, composed .claude/rules/<name>.md

Identity is authored once, by whoever builds the stack. Rules are contributed by whichever recipes are in the stack's recipes: list. Both need deriving into whatever the target harness natively understands.

Identity resolution — prefer a real system-prompt hook, fall back to an instructions file

Not every harness has a literal "modify the system prompt" primitive. Where one exists, prefer it (more direct, doesn't compete with the harness's own default instructions-file semantics). Where it doesn't, fall back to appending into that harness's instructions-file.

Harness System-prompt hook? Target Baked or host-mounted? Merge complexity
claude No separate hook — CLAUDE.md is the only surface .claude/CLAUDE.md Profile-baked ($XDG_DATA_HOME/.../profiles/<stack>/.claude/) New file, no existing content to merge with
omp ~/.omp/agent/APPEND_SYSTEM.md exists as a mechanism, but no viable write path — see below AGENTS.md-equivalent fallback only, for now Host-shared (~/.omp/agent is rw-mounted from the real host dir, per _omp_agent_mount — same dir used by every omp stack AND native host omp) Blocked — see resolution below
codex model_instructions_file = "..." exists, but it replaces Codex's built-in instructions entirely (confirmed: "the heavier hammer... replaces built-in instructions"), same category as omp's SYSTEM.mdnot an addition. Given the append-first principle, this is not the default target. Default to AGENTS.md (append/context-style, global ~/.codex/AGENTS.md); leave model_instructions_file as an opt-in escape hatch for a stack that explicitly wants full override Image-baked, profile-scoped (Dockerfile.harnessed-codex already writes ~/.codex/config.toml with [mcp_servers.hatago]) AGENTS.md is a fresh file, no merge. model_instructions_file (if ever used) needs a TOML merge into the existing baked config.toml.
opencode Define a custom agent in opencode.json ("prompt": "{file:./prompts/my-persona.md}") and invoke with opencode --agent <name>confirmed safe: agent-level permissions inherit from global config unless explicitly overridden (most default to "allow"), so an agent defined with only a custom prompt behaves identically to default otherwise. Custom agent + changed attach command Image-baked (Dockerfile.harnessed-opencode already writes ~/.config/opencode/opencode.json with the hatago MCP entry) JSON merge required (simpler than TOML) + _HARNESS_ATTACH_CMD["opencode"] needs to become stack-conditional, not a fixed per-harness string as it is today
antigravity gemini.md (project root, auto-loaded) or ~/.gemini/antigravity-cli/settings.json's context.fileName: [...] to point at a global file — confirmed safe to bake: settings.json is explicitly marked "Surface? No" in home-folder-antigravity-requirements.md (this repo) — never host-mounted, unlike the conversation/history dirs it sits alongside. Bake settings.json + the referenced .md, same pattern as the already-baked mcp_config.json Image-baked, profile-scoped — no conflict (resolved; see below) New files, no existing content to merge with

omp: the bridge doesn't do what I assumed — verified from source, not guessed

I downloaded @drmikecrowe/omp-claude-hooks-bridge@0.2.2 from npm and read index.ts directly (not just its README). It is purely a hook-execution bridge — it maps Claude Code's SessionStart/UserPromptSubmit/PreToolUse/PostToolUse/Stop hooks onto omp's extension lifecycle events (session_start, before_agent_start, tool_call, tool_result, agent_end) and runs the configured shell commands. Checked the session_start handler specifically (notifySessionStartHookResult, index.ts:899) — it only posts a UI notification (ctx.ui.notify(...)). It never reads hookSpecificOutput.additionalContext and has no content-injection capability at all. The earlier assumption that this bridge was "already the integration point for Claude-canonical content reaching omp" was wrong — it bridges hook execution, not instructions/context.

Real options for omp, neither clean:

  1. Guarded, idempotent append into the shared ~/.omp/agent/APPEND_SYSTEM.md/RULES.md — delimited markers (<!-- harnessed:stack:<name> -->...<!-- /harnessed -->), same pattern as the existing CLAUDE.md nudge trick in the Superpowers/hyperpowers PLAN.md docs. Ships now; accepts that multiple omp-harness stacks share one file (their content coexists via markers, not a true per-stack profile-scoped file).
  2. Contribute real injection support to omp-claude-hooks-bridge itself (e.g. handle additionalContext from a SessionStart hook the way Claude Code's own SessionStart mechanism does, then have session_start's handler feed it into omp's context) — clean, profile-scoped, but real new development in an external repo (yours), not part of this implementation.

Decision needed: ship (1) now and treat (2) as a future upstream contribution, or block omp entirely on (2)? Not yet decided.

Rules concatenation — for harnesses without directory-based rule loading

Once the T10 flat-file bug is fixed, .claude/rules/*.md (recipe-contributed, composed per stack) is the canonical source. Harnesses that read it directly (claude; antigravity via .agents/rules/, same flat-file shape) need no extra work beyond mirroring. Harnesses with no directory-based rules concept need a derived, concatenated single file built at assemble time:

  • codex: no directory-rules primitive at all — concatenate into AGENTS.md (same file as the identity fallback target above; mind the 32 KiB project_doc_max_bytes cap when both share one file — identity content plus every rule could hit it sooner than either alone).
  • omp: RULES.md is the native analog (global + project both apply, unlike SYSTEM.md's nearest-wins) — same host-shared-write problem as identity above, same two options (guarded append now, or real bridge injection later).
  • opencode: has native glob support in instructions: (its opencode.json instructions: array), so it may not need concatenation at all — point it at .claude/rules/*.md directly using an absolute container path (/home/harnessed/.claude/rules/*.md), not a bare relative path — all of opencode's own doc examples resolve paths project-relative, and the profile lives under container $HOME, not the mounted project.

paths:-conditional semantics — decided, not just a leaning. Claude Code rules can carry paths: frontmatter for conditional loading (only active when a matching file enters context). Decision: don't support this outside Claude. Every other harness concatenates everything and loads it always-on. This is an accepted, permanent limitation, not a gap to close later — Claude gets true conditional rule loading; every other harness gets "all rules, all the time."

Resolved questions (2026-07-02)

All four original open questions are now resolved:

  1. Antigravity settings.json vs. host-mounted history dirno conflict. Re-read home-folder-antigravity-requirements.md's own disposition table: settings.json is explicitly marked "Surface? No" — never host-mounted, same treatment as the already-baked mcp_config.json. Safe to bake identity config there.
  2. omp-claude-hooks-bridge hook APIno injection surface exists. Confirmed by reading the actual package source (not just its README): it's a hook-execution bridge only, no additionalContext handling anywhere. See the omp section above for the resulting two options (guarded shared-file append now, or real bridge development later) — still an open decision, not a technical unknown anymore.
  3. opencode --agent <name> side effectsconfirmed safe. Agent permissions inherit from global config unless explicitly overridden; a custom agent with only a prompt field behaves identically to default otherwise.
  4. Rules concatenation semanticsdecided, not just resolved: no paths:-conditional support outside Claude, ever. Concatenate everything for every other harness.

omp decision (2026-07-02): ship via guarded shared-file append. Write into ~/.omp/agent/{APPEND_SYSTEM,RULES}.md with idempotent, delimiter-marked blocks per stack (same pattern as the existing CLAUDE.md nudge trick). Works today; shared across all omp usage on the host, not profile-scoped — an accepted, documented tradeoff, not a blocker. Real injection support in omp-claude-hooks-bridge stays a future upstream improvement, not a prerequisite.

Suggested phasing

  1. T-rules-fix: fix the flat-file bug in synclinks.py/schema.py for rules: — small, isolated, no design dependencies. Do this regardless of the rest.
  2. Stack-level instructions: field (schema + assemble → .claude/CLAUDE.md) — claude only, no cross-harness complexity, proves the two-tier model end-to-end.
  3. antigravity (bake settings.json + identity .md, same pattern as the existing mcp_config.json bake) — no longer blocked, genuinely simple now.
  4. codex (write ~/.codex/AGENTS.md, no merge needed since it's a fresh file, not model_instructions_file) — self-contained, no host-mount risk.
  5. opencode (JSON merge into the existing baked opencode.json + conditional attach command) — more launcher surface area than codex/antigravity.
  6. omp — needs the guarded-append-vs-bridge-development decision above before starting.

See also

  • docs/research/harness-rules-directory-support.md — the per-harness native rules-directory research this spec builds on.
  • docs/research/home-folder-antigravity-requirements.md — the antigravity host-mount design this spec's open question 1 depends on.
  • src/harnessed/synclinks.py — current (buggy, for rules:) fan-out mechanism.
  • catalog/agents/{omp,codex,opencode,antigravity}/agent.yaml, catalog/base/Dockerfile.harnessed-* — the per-harness images this spec's baked-config changes would touch.

Clone this wiki locally