You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Allow each persona to attach a hand-authored CLAUDE.md / AGENTS.md to itself — job-scoped context that travels with the persona, materialized into the sandbox mount at session time so the harness reads it natively.
This fits the AgentWorkforce value prop: job-scoped + team-shareable + anti-bloat. Users get the right context only when that persona runs, instead of stuffing everything into a global CLAUDE.md.
Design
Schema
Add to PersonaSpec (top-level, sibling of tiers) and PersonaRuntime (per-tier):
Mirror on LocalPersonaOverride and its tier shape. Path is resolved relative to the JSON file that declared the field (matters for extends cascades).
Validation (paths): non-empty, no leading /, no .., must end in .md. Validation (modes): enum check; reject mode set without a corresponding path.
Resolution
For a given (persona, selected tier, harness) at runtime:
Pick the field by harness:
claude → claudeMd
opencode → agentsMd
codex → none (warn + skip; no mount available)
Tier-level wins over top-level. Mode resolves with the same cascade and is independent of the path (a tier can override the path while inheriting the mode, or vice versa).
No cross-fallback between claudeMd and agentsMd. If you want one file applied everywhere, set both top-level fields to the same path.
Runtime delivery — write into the sandbox mount
In runInteractive (packages/cli/src/cli.ts:558+), after the mount is established and before harness invocation:
claude in sandbox: materialize <mountDir>/CLAUDE.md. CLEAN_IGNORED_PATTERNS (cli.ts:429-432) already excludes CLAUDE.md from sync — no real-repo pollution.
opencode in sandbox: materialize <mountDir>/AGENTS.md. Action item: add AGENTS.md to CLEAN_IGNORED_PATTERNS (it is currently not excluded, so without this change it would sync back into the user's real repo).
codex / --install-in-repo: no mount available → log a warning and continue. Never touch the user's real cwd.
Why mount-write rather than appending to systemPrompt? Native CLAUDE.md / AGENTS.md semantics — @import, hierarchy, etc. — survive. The harness reads its own convention.
packages/harness-kit/src/harness.ts does not change.
Mode behavior
Applies to both claude and opencode mount-write paths:
overwrite (default): write only the persona's resolved markdown content into <mountDir>/CLAUDE.md (or AGENTS.md). The user's real-cwd file at the same name is hidden by the existing sandbox sync exclusion.
extend: read the user's real-cwd CLAUDE.md (or AGENTS.md) if it exists, then write <real-content>\n\n---\n\n<persona-content> into the mount file. If the real file does not exist, behave as overwrite (graceful degradation, no warning). The result still lives only in the mount; the real file is never modified.
extends / merge
In mergeOverride (packages/cli/src/local-personas.ts:666-712):
Top-level claudeMd / agentsMd (and the modes): override replaces base wholesale, matching description semantics.
Path is anchored to the directory of whichever JSON declared it. Track sourcePath per parsed override internally (set in readLayerDir, local-personas.ts:283-296); resolved PersonaSpec carries the already-absolute path so downstream code doesn't re-track sources.
Built-in catalog — inline at build time
Built-in personas in personas/*.json get content inlined at generation time so installed packages don't need to ship sibling .md files for built-ins:
packages/workload-router/scripts/generate-personas.mjs: read sibling .md (relative to personas/), emit claudeMdContent / agentsMdContent (string) on the generated spec, drop the path field.
Runtime checks *Content first, otherwise reads from the absolute path.
Hard-fail the generator on a missing built-in .md.
Watch mode (lines 67-87) extends to retrigger on .md changes.
Packaging (prpm install) — targeted asset copy
packages/cli/src/persona-install.ts currently flat-copies .json only (collectJsonFiles, line 165). Extend it:
Replace collectJsonFiles with collectPersonaAssets — for each persona JSON, read its claudeMd / agentsMd (top-level + per-tier), resolve each relative to the JSON's dir, and add to a per-persona asset list. Reject .. / absolute paths. Hard-fail on missing files at packaging time (loud failure is right at the distribution boundary).
Extend PersonaFile (line 58) with assets: { sourcePath, basename }[].
In installPersonas (332-384): copy each persona's assets into <targetDir>/<id>__assets/<basename> and rewrite the JSON's claudeMd / agentsMd paths in-place to point at the new location.
Validation
parseOverride (local-personas.ts:304-363) and assertTiersShape (477-500): assert string + non-empty + ends in .md + no .. + not absolute. Reuse the style of assertSafeRelativePath (cli.ts:407-422).
After cascade resolution: stat the absolute path; missing file → push to load warnings and clear the field on the resolved spec (graceful in dev).
Generator: hard-fail on missing built-in .md.
Files affected
packages/workload-router/src/index.ts — extend PersonaRuntime (lines 59-64) and PersonaSpec (140-175) with claudeMd?, claudeMdMode?, agentsMd?, agentsMdMode?, plus claudeMdContent? / agentsMdContent? for inlined built-ins.
packages/cli/src/local-personas.ts — extend LocalPersonaOverride (30-57); validate in parseOverride (304-363) and assertTiersShape (477-500); track per-override source path in readLayerDir (267-298); resolve absolute paths and stat in mergeOverride (666-712) and standaloneSpecFromOverride (566-603); surface missing-file warnings in loadLocalPersonas (734-768).
packages/cli/src/cli.ts — add AGENTS.md to CLEAN_IGNORED_PATTERNS (429-432); carry resolved sidecar info onto PersonaSelection in buildSelection (236-253); in runInteractive (558+), pick field by harness, materialize content into the mount dir before harness invoke (with mode handling); warn-and-skip for codex / --install-in-repo.
packages/cli/src/persona-install.ts — extend PersonaFile (58); replace collectJsonFiles (165) with collectPersonaAssets; update collectPersonas (212) and installPersonas (332) to copy referenced sidecars and rewrite JSON paths.
Rejects .. and absolute paths at pack/install time
Hard-fail on missing referenced .md
CLI / runtime (packages/cli/src/cli.test.ts)
Resolution: claude → claudeMd; opencode → agentsMd; codex → warn + skip; tier-level wins over top-level; no cross-fallback
overwrite mode: <mountDir>/CLAUDE.md contains exactly the persona content (claude) / <mountDir>/AGENTS.md (opencode)
extend mode: real cwd has CLAUDE.md → mount file = <real>\n\n---\n\n<persona>; real cwd has no file → mount file = persona content only (no warning)
Real cwd is never mutated in either mode
--install-in-repo: warn + no file written into real cwd
Generator (snapshot)
Built-in persona referencing a sibling .md produces generated TS with *Content populated and the path field gone
Missing .md hard-fails the generator
Manual smoke
Author a local persona with claudeMd (overwrite), run agentworkforce agent <persona>@best (claude), confirm the agent acknowledges the contents on first turn
Same persona with claudeMdMode: "extend" in a repo that has its own CLAUDE.md — confirm both sets of context are visible to the agent
Repeat for opencode tier (agentsMd)
Out of scope
Native CLAUDE.md @import / hierarchy semantics for codex (codex has no mount and no file convention support in this repo).
--install-in-repo mode — never write into the user's real repo; warn + skip.
Cross-fallback between claudeMd and agentsMd.
Multiple sidecars per (persona, tier).
Inline content in JSON (only path form is accepted from authors; build-time inlining is internal to the catalog generator).
Hot-reload of sidecar content during a live session.
Distinction from skills (which are declared by URL and materialized at session time): sidecar markdown files are author-shipped along with the persona JSON and copied at install time.
Summary
Allow each persona to attach a hand-authored
CLAUDE.md/AGENTS.mdto itself — job-scoped context that travels with the persona, materialized into the sandbox mount at session time so the harness reads it natively.This fits the AgentWorkforce value prop: job-scoped + team-shareable + anti-bloat. Users get the right context only when that persona runs, instead of stuffing everything into a global
CLAUDE.md.Design
Schema
Add to
PersonaSpec(top-level, sibling oftiers) andPersonaRuntime(per-tier):Mirror on
LocalPersonaOverrideand its tier shape. Path is resolved relative to the JSON file that declared the field (matters forextendscascades).Validation (paths): non-empty, no leading
/, no.., must end in.md.Validation (modes): enum check; reject mode set without a corresponding path.
Resolution
For a given (persona, selected tier, harness) at runtime:
claude→claudeMdopencode→agentsMdcodex→ none (warn + skip; no mount available)claudeMdandagentsMd. If you want one file applied everywhere, set both top-level fields to the same path.Runtime delivery — write into the sandbox mount
In
runInteractive(packages/cli/src/cli.ts:558+), after the mount is established and before harness invocation:<mountDir>/CLAUDE.md.CLEAN_IGNORED_PATTERNS(cli.ts:429-432) already excludesCLAUDE.mdfrom sync — no real-repo pollution.<mountDir>/AGENTS.md. Action item: addAGENTS.mdtoCLEAN_IGNORED_PATTERNS(it is currently not excluded, so without this change it would sync back into the user's real repo).--install-in-repo: no mount available → log a warning and continue. Never touch the user's real cwd.Why mount-write rather than appending to
systemPrompt? Native CLAUDE.md / AGENTS.md semantics —@import, hierarchy, etc. — survive. The harness reads its own convention.packages/harness-kit/src/harness.tsdoes not change.Mode behavior
Applies to both claude and opencode mount-write paths:
overwrite(default): write only the persona's resolved markdown content into<mountDir>/CLAUDE.md(orAGENTS.md). The user's real-cwd file at the same name is hidden by the existing sandbox sync exclusion.extend: read the user's real-cwdCLAUDE.md(orAGENTS.md) if it exists, then write<real-content>\n\n---\n\n<persona-content>into the mount file. If the real file does not exist, behave asoverwrite(graceful degradation, no warning). The result still lives only in the mount; the real file is never modified.extends/ mergeIn
mergeOverride(packages/cli/src/local-personas.ts:666-712):claudeMd/agentsMd(and the modes): override replaces base wholesale, matchingdescriptionsemantics.Path is anchored to the directory of whichever JSON declared it. Track
sourcePathper parsed override internally (set inreadLayerDir,local-personas.ts:283-296); resolvedPersonaSpeccarries the already-absolute path so downstream code doesn't re-track sources.Built-in catalog — inline at build time
Built-in personas in
personas/*.jsonget content inlined at generation time so installed packages don't need to ship sibling.mdfiles for built-ins:packages/workload-router/scripts/generate-personas.mjs: read sibling.md(relative topersonas/), emitclaudeMdContent/agentsMdContent(string) on the generated spec, drop the path field.*Contentfirst, otherwise reads from the absolute path..md..mdchanges.Packaging (
prpm install) — targeted asset copypackages/cli/src/persona-install.tscurrently flat-copies.jsononly (collectJsonFiles, line 165). Extend it:collectJsonFileswithcollectPersonaAssets— for each persona JSON, read itsclaudeMd/agentsMd(top-level + per-tier), resolve each relative to the JSON's dir, and add to a per-persona asset list. Reject../ absolute paths. Hard-fail on missing files at packaging time (loud failure is right at the distribution boundary).PersonaFile(line 58) withassets: { sourcePath, basename }[].installPersonas(332-384): copy each persona's assets into<targetDir>/<id>__assets/<basename>and rewrite the JSON'sclaudeMd/agentsMdpaths in-place to point at the new location.Validation
parseOverride(local-personas.ts:304-363) andassertTiersShape(477-500): assert string + non-empty + ends in.md+ no..+ not absolute. Reuse the style ofassertSafeRelativePath(cli.ts:407-422).warningsand clear the field on the resolved spec (graceful in dev)..md.Files affected
packages/workload-router/src/index.ts— extendPersonaRuntime(lines 59-64) andPersonaSpec(140-175) withclaudeMd?,claudeMdMode?,agentsMd?,agentsMdMode?, plusclaudeMdContent?/agentsMdContent?for inlined built-ins.packages/workload-router/scripts/generate-personas.mjs— read sibling.md, emit*Content, watch.mdchanges, hard-fail on missing.packages/cli/src/local-personas.ts— extendLocalPersonaOverride(30-57); validate inparseOverride(304-363) andassertTiersShape(477-500); track per-override source path inreadLayerDir(267-298); resolve absolute paths and stat inmergeOverride(666-712) andstandaloneSpecFromOverride(566-603); surface missing-file warnings inloadLocalPersonas(734-768).packages/cli/src/cli.ts— addAGENTS.mdtoCLEAN_IGNORED_PATTERNS(429-432); carry resolved sidecar info ontoPersonaSelectioninbuildSelection(236-253); inrunInteractive(558+), pick field by harness, materialize content into the mount dir before harness invoke (with mode handling); warn-and-skip for codex /--install-in-repo.packages/cli/src/persona-install.ts— extendPersonaFile(58); replacecollectJsonFiles(165) withcollectPersonaAssets; updatecollectPersonas(212) andinstallPersonas(332) to copy referenced sidecars and rewrite JSON paths.packages/harness-kit/src/harness.ts— no change.Test plan
packages/cli/src/local-personas.test.ts).., absolute, non-.md)claudeMd, base does not, and vice-versa; resolved spec carries absolute path anchored to the right layer's dirpackages/cli/src/persona-install.test.ts)npm pack→ install copies referenced.mdfiles into<id>__assets/, rewrites JSON paths, leaves unreferenced.mdfiles behind..and absolute paths at pack/install time.mdpackages/cli/src/cli.test.ts)claudeMd; opencode →agentsMd; codex → warn + skip; tier-level wins over top-level; no cross-fallbackoverwritemode:<mountDir>/CLAUDE.mdcontains exactly the persona content (claude) /<mountDir>/AGENTS.md(opencode)extendmode: real cwd hasCLAUDE.md→ mount file =<real>\n\n---\n\n<persona>; real cwd has no file → mount file = persona content only (no warning)--install-in-repo: warn + no file written into real cwd.mdproduces generated TS with*Contentpopulated and the path field gone.mdhard-fails the generatorclaudeMd(overwrite), runagentworkforce agent <persona>@best(claude), confirm the agent acknowledges the contents on first turnclaudeMdMode: "extend"in a repo that has its ownCLAUDE.md— confirm both sets of context are visible to the agentagentsMd)Out of scope
@import/ hierarchy semantics for codex (codex has no mount and no file convention support in this repo).--install-in-repomode — never write into the user's real repo; warn + skip.claudeMdandagentsMd.Related