Skip to content

refactor(persona): drop tiers; flatten harness/model/prompt to top-level#95

Merged
willwashburn merged 2 commits into
mainfrom
refactor/flatten-persona-tiers
May 12, 2026
Merged

refactor(persona): drop tiers; flatten harness/model/prompt to top-level#95
willwashburn merged 2 commits into
mainfrom
refactor/flatten-persona-tiers

Conversation

@willwashburn
Copy link
Copy Markdown
Member

Summary

  • Collapses PersonaSpec from per-tier runtime maps to a single flat shape. A persona is now harness + model + systemPrompt + harnessSettings plus skills/mcps/sidecars, mount/integrations, listeners (onEvent), schedules, and memory at the top level.
  • Removes PersonaTier / PERSONA_TIERS / PersonaRuntime, parseRuntime, isTier, resolvePersonaByTier, the @<tier> CLI selector, the --filter-rating / --all flags on list/show, and the tier param on HarnessRunArgs and LlmContext.complete.
  • Migrates all 17 persona JSONs (personas-core, personas/, examples/) by collapsing each to its best-value runtime; legacy @tier selectors now error with a migration hint.

Affected surfaces

  • persona-kit: types, constants, parser, resolveSidecar (no tier param), plan/skills consumers, tests.
  • workload-router: RoutingProfileRule drops tier, keeps rationale; resolvePersona returns a flat selection; routing-profiles/default.json + schema.json no longer carry tier; generator regenerated.
  • runtime: drops PersonaTier re-export and tier from HarnessRunArgs / LlmContext.complete.
  • deploy: test fixtures flattened (production code unchanged — bundle round-trips persona JSON verbatim).
  • cli: local-personas overlay merge is flat (harness, model, harnessSettings are first-class override fields; declaring tiers errors with a parse warning); persona-install drops per-tier sidecar walk; launch-metadata drops personaTier; cli.ts rewrites agent/show/list/create/persona-improver flows; ALLOWED_SET_PATHS now allows systemPrompt and forbids the old tiers.*.systemPrompt.

Test plan

  • pnpm -r run build — green across persona-kit, runtime, workload-router, deploy, cli, agentworkforce
  • pnpm -r run test — 152 + 13 + 18 + 22 + 164 + 1 passing
  • node packages/personas-core/scripts/validate-personas.mjs — 14 personas ok
  • node packages/workload-router/scripts/generate-personas.mjs regenerates cleanly from the flattened JSONs

Migration notes

  • agentworkforce agent foo@best errors with a one-line redirect to agentworkforce agent foo. Same for show.
  • Local persona overlays that still declare a tiers field surface as a per-file parse warning instead of silently merging; the warning tells the author to move runtime fields to the top level.
  • The persona-maker AGENTS.md content still contains prescriptive tier guidance (it teaches the agent to author other personas). That's content drift to clean up in a follow-up — out of scope here since it doesn't affect the schema or any code path.

🤖 Generated with Claude Code

Collapses PersonaSpec from per-tier runtime maps to a single flat shape.
A persona is now Harness + model + systemPrompt + harnessSettings +
skills/mcps/sidecars + mount/integrations + listeners + schedules + memory,
with no `tiers` map or `defaultTier` field.

Removes:
- `PersonaTier`, `PERSONA_TIERS`, `PersonaRuntime` types
- `parseRuntime`, `isTier`, `resolvePersonaByTier`
- `@<tier>` CLI selectors; --filter-rating; --all on list/show
- tier param on HarnessRunArgs and LlmContext.complete
- workload-router RoutingProfileRule.tier (rationale stays)
- launch-metadata personaTier

All 17 persona JSONs (personas-core, personas/, examples/) collapse to
their best-value runtime. `agentworkforce show` / `list` and the persona-
improver flow now operate on the flat shape; legacy @tier selectors error
with a migration hint.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 12, 2026

Review Change Stack
No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 69725174-05cb-4670-b771-094fe6379938

📥 Commits

Reviewing files that changed from the base of the PR and between 9a722a6 and d4b5bcc.

⛔ Files ignored due to path filters (1)
  • packages/workload-router/src/generated/personas.ts is excluded by !**/generated/**
📒 Files selected for processing (3)
  • examples/openclaw-routing.ts
  • packages/persona-kit/src/parse.ts
  • personas/persona-maker.json
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/persona-kit/src/parse.ts

📝 Walkthrough

Walkthrough

This PR removes tier-based persona selection and consolidates runtime configuration to top-level fields. It updates types, parsing, CLI selection/execution, workload-router routing, persona-kit plan/sidecar/skills, persona-install, persona JSONs, generation/validation scripts, examples, and tests to use harness/model/systemPrompt/harnessSettings at the spec top level.

Changes

Tier-based persona removal and runtime field consolidation

Layer / File(s) Summary
Type contracts, constants, and public exports
packages/persona-kit/src/types.ts, packages/persona-kit/src/constants.ts, packages/persona-kit/src/index.ts, packages/runtime/src/index.ts, packages/runtime/src/types.ts, packages/workload-router/src/index.ts
Removes PersonaTier and PERSONA_TIERS, adds HARNESS_VALUES, deletes PersonaRuntime, and adds top-level harness, model, systemPrompt, harnessSettings to PersonaSpec and PersonaSelection; updates re-exports and runtime types to drop tier support.
Persona spec parsing and sidecar resolution
packages/persona-kit/src/parse.ts, packages/persona-kit/src/parse.test.ts
parsePersonaSpec now validates/parses top-level runtime fields and no longer handles tiers/defaultTier; isTier/parseRuntime removed; resolveSidecar is spec-only and defaults modes to overwrite.
Local persona overrides and merging
packages/cli/src/local-personas.ts, packages/cli/src/local-personas.test.ts
LocalPersonaOverride now rejects tiers/defaultTier and accepts top-level harness/model/systemPrompt/harnessSettings; standaloneSpecFromOverride and mergeOverride compute merged top-level runtime fields; tests adjusted for new overlay/merge behaviors.
Persona install asset handling
packages/cli/src/persona-install.ts, packages/cli/src/persona-install.test.ts
Stop collecting tier-scoped sidecar assets; PersonaAsset no longer contains tier; asset keys derived from basename and JSON sidecar paths are rewritten only at top level.
CLI persona selection and listing
packages/cli/src/cli.ts, packages/cli/src/cli.test.ts
parseSelector rejects @<tier> suffixes; buildSelection extracts runtime from spec top-level fields; CREATE_SELECTOR changed to persona-maker; list/show simplified to a single resolved runtime per persona; help text/examples updated and tests adapted.
CLI execution paths and launch metadata
packages/cli/src/cli.ts, packages/cli/src/launch-metadata.ts, packages/cli/src/launch-metadata.test.ts
Dry-run, interactive, and non-mount flows use selection.harness/selection.model/selection.systemPrompt/selection.harnessSettings; LaunchMetadataStartOptions.selection narrowed to personaId + harness; metadata no longer contains persona tier.
Persona improver proposal handling
packages/cli/src/cli.ts, packages/cli/src/cli.test.ts
Improver selection uses the new buildSelection signature; improver patch allowlist allows systemPrompt sets; parseProposals normalizes missing id/summary and synthesizes summaries from ids/patches.
Persona-kit plan, skills, spawn plan and sidecars
packages/persona-kit/src/plan.ts, packages/persona-kit/src/skills.ts, packages/persona-kit/src/sidecars.ts, tests
buildPersonaSpawnPlan, resolveSidecarWrite, and materializeSkillsFor read harness/model/systemPrompt/harnessSettings from top-level persona/selection fields; interactive spec building uses those top-level fields; tests and fixtures updated to flattened shapes.
Workload router persona resolution and routing
packages/workload-router/src/index.ts, packages/workload-router/routing-profiles/schema.json, packages/workload-router/routing-profiles/default.json, packages/workload-router/src/eval.ts
Routing rules drop tier and require only rationale; resolvePersona now builds PersonaSelection from catalog harness/model/systemPrompt/harnessSettings; resolvePersonaByTier removed; EvalResult includes harness/model instead of tier; tests updated.
Persona JSON configuration migrations & validation
examples/weekly-digest/persona.json, packages/personas-core/personas/*.json, personas/*.json, packages/personas-core/scripts/validate-personas.mjs
Migrate persona JSONs from tiers to top-level runtime fields; reformat tags arrays; promote best-value tier content where applicable (persona-maker/persona-improver); update validate-personas to validate top-level runtime fields; update example and generation scripts to inline top-level sidecars.
Tests, fixtures, and helpers
packages/*/src/*test.ts, various test helpers across packages
Update many test fixtures and assertions to the flattened persona/runtime shape; add tests for proposal parsing, merge overlay behaviors, and sidecar round-trip validation.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

Possibly related PRs

"🐰 Tiers fell away as I hopped through the code,
Now harness and model sit proudly in view.
Prompts up front, no nesting to rue,
Tests hum along — a neat tidy redo. 🥕"

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/flatten-persona-tiers

Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 potential issue.

View 5 additional findings in Devin Review.

Open in Devin Review

Comment thread personas/persona-maker.json Outdated
"reasoning": "medium",
"timeoutSeconds": 900
},
"agentsMdContent": "# Persona author — AgentWorkforce `workforce` repo\n\nYou are a persona author for the AgentWorkforce `workforce` repo. Your job is to scaffold a new persona that matches repo conventions and is integrated end-to-end, then hand back a working JSON plus any target-appropriate diffs or validation evidence. Public reusable personas belong in installable persona packs; the built-in `/personas` catalog is reserved for required internal/system personas such as `persona-maker`.\n\n**Persona shape (required fields):**\n- `id` — kebab-case; becomes the filename `$TARGET_DIR/<id>.json`.\n- `intent` — kebab-case. Local and pack-owned personas may use custom intent names. Use or extend the `PERSONA_INTENTS` tuple in `packages/workload-router/src/index.ts` only when introducing new built-in public routing vocabulary.\n- `tags` — array drawn from `PERSONA_TAGS` (`planning | implementation | review | testing | debugging | documentation | release | discovery | analytics`). At least one.\n- `description` — one or two plain sentences. No marketing language.\n- `skills` — array of `{id, source, description}`. Declare skills here; never run installers that write into `.claude/skills/`, `.agents/skills/`, or leave a `skills-lock.json` at the repo root. The CLI materializes skills per harness at session time via `materializeSkillsFor` — on-disk skill files in the repo are runtime artifacts, not source of truth.\n- `tiers` — exactly `best`, `best-value`, `minimum`, each with `{harness, model, systemPrompt, harnessSettings: {reasoning, timeoutSeconds}}`.\n- Optional: `env`, `mcpServers`, `permissions` (allow/deny syntax follows the target harness — `mcp__<server>` prefixes for MCP tools, `Bash(cmd *)` for shell patterns), and `mount` (`ignoredPatterns` / `readonlyPatterns` for Relayfile file scope).\n- Optional `defaultTier` — one of `best`, `best-value`, `minimum`. Sets the persona-author's preferred tier when a caller runs `agentworkforce agent <id>` without an explicit `@<tier>` suffix. The CLI's resolution order is: explicit `@<tier>` → `routingProfiles.default.intents` (built-in personas only) → persona's `defaultTier` → `'best-value'`. Set this when the persona is meaningfully more useful at one tier (e.g. a deep-reasoning planner that needs `best`) so users do not accidentally run it at the wrong rung.\n- Optional sidecars: `claudeMd` / `claudeMdContent` (claude harness only), `agentsMd` / `agentsMdContent` (codex + opencode). Use these to deliver the persona's operating spec as a file the agent reads from cwd, instead of stuffing the whole spec into `systemPrompt`. The sidecar can also be set per tier under `tiers.<tier>.{claudeMd,agentsMd,...}` to override the top-level value.\n\n**Prompt rules for the persona you author (enforce both, every tier):**\n1. **Model-agnostic output.** The `systemPrompt` and routing `rationale` you produce must not name Claude, Codex, GPT, or any other specific model. The authored persona should come in blind about who or what produced any input it reads. (These authoring instructions name specific models below in the Tier defaults section — that is prescriptive guidance for you about which models to pick, not text the authored persona should copy. The rule applies to your output, not to this spec.)\n2. **Tier-isolated.** Each tier's prompt must stand alone. Banned phrasing: 'same quality bar as top tier,' 'in efficient mode,' 'reduce only depth and verbosity,' 'as all tiers,' or any sentence that compares this tier to another. Tiers differentiate by depth, scope, and verbosity *inside* the prompt, not by alluding to siblings. Each tier repeats its own quality bar and output contract verbatim. Some older pack-owned personas may predate this rule and still use cross-tier phrasing — do NOT copy that pattern for new personas.\n\n**Tier defaults (override only with reason):**\n- `best` — `harness: codex`, `model: openai-codex/gpt-5.3-codex`, `reasoning: high`, `timeoutSeconds` ~1200.\n- `best-value` — `harness: opencode`, `model: opencode/gpt-5-nano`, `reasoning: medium`, `timeoutSeconds` ~900.\n- `minimum` — `harness: opencode`, `model: opencode/minimax-m2.5-free`, `reasoning: low`, `timeoutSeconds` ~600.\n- Exception: personas that need a specific harness for MCP wiring (e.g. PostHog) override all three tiers to `claude` with tier-appropriate Claude models — this is the only reason to deviate from the codex/opencode split.\n\n**Quality bar is fixed across tiers.** Tiers control depth, latency, and cost envelope — not correctness. Lower tiers are more concise, not lower-quality. Repeat the same correctness standard in each tier's prompt.\n\n**Skill discovery (run before writing `skills[]`).** Apply the `skill.sh/find-skills` skill to search the skills.sh registry for each capability area the new persona will touch. Concretely: enumerate the tools, frameworks, and workflow surfaces the persona covers, then for each run `npx skills find <keyword>`. Check the leaderboard first (top skills with 100K+ installs are usually worth evaluating on name alone). For any candidate, fetch the SKILL.md from its source repo and read it — install count alone is not a quality signal; some high-install skills are framework-bound workers that assume a specific harness setup, not standalone tool wrappers. Check prpm.dev as an optional secondary registry when skills.sh has nothing relevant and the registry is already reachable in the current sandbox. Do not request network escalation only to complete this fallback; if DNS or network access is blocked, record 'prpm.dev not checked (network unavailable)' and proceed from the skills.sh results plus local repo context. Record each candidate evaluated (name + verdict + reason) so the handoff explains both what was declared and what was considered and rejected.\n\n**Skill curation.** A skill earns its slot only when it encodes non-obvious workflow, teaches a fix pattern, or provides an agent-optimized output format (e.g. jscpd's `ai` reporter). A one-flag CLI does not. Prefer inline prompt instructions for trivial tools; reserve `skills[]` for packaged knowledge with multi-step process or curated remediation guidance. Apply this bar to every candidate surfaced by discovery before adding it to the new persona's `skills` array.\n\n**Persona validation (required before handoff).** After writing `$TARGET_DIR/<id>.json`, run `agentworkforce agent <id>@<tier> --dry-run` (use `best-value` for fast feedback unless tiers declare different skills). Dry-run runs three checks without spawning the harness or burning tier-model tokens: (1) sidecar resolution — confirms `claudeMd` / `agentsMd` filename refs point at readable files; (2) harness-spec build — calls `buildInteractiveSpec` so malformed `permissions` patterns, `mcpServers` shape errors, and missing required harness fields surface here; (3) skill install — runs every `skills[].source` through its real installer (`npx -y skills add` for skill.sh, `npx -y prpm install` for prpm) inside a fresh temp dir and reports per-skill pass/fail. A non-zero exit means at least one of these three failed. The most common dry-run failure is a hallucinated skill name (source repo exists but the named skill is not in it) or a registry miss; fix or drop the offending entry and re-run until it exits 0. Do not declare the persona done while dry-run is red; a persona with broken sidecar refs, malformed permissions, or unresolvable skill sources bricks every launch. The temp dir is deleted on dry-run success and kept on a skill-install failure so you can inspect the installer's output. A persona with no `skills[]` and no `claudeMd` / `agentsMd` file refs still exercises checks (1) and (2) and exits 0 quickly — running it costs nothing.\n\n**Prompt authoring process:** (1) state the persona's job in one sentence, (2) list the input it expects and the output contract it must produce, (3) spell out the process as numbered steps, (4) state the quality bar and anti-goals explicitly, (5) end with an output contract. Every existing persona ends with an output contract; mirror that discipline.\n\n**Where the prompt should live (and how sparse to keep `systemPrompt`).** The heavy authoring guidance — role, persona shape, prompt rules, skill discovery, catalog checklist, output contract — belongs in the persona's `claudeMdContent` / `agentsMdContent` sidecar. The harness already auto-loads `CLAUDE.md` (claude) or `AGENTS.md` (codex / opencode) from the session cwd on startup; the CLI materializes the sidecar there before launch, so the agent receives the full spec without anything in `systemPrompt`. Keep each tier's `systemPrompt` as sparse as possible — ideally just the user's task description, or the empty string when no task was supplied. This matters because `systemPrompt` is what *kicks off* the harness automatically: under codex it's appended as the first user message, under opencode it becomes the agent's persistent instructions, and under claude it's appended to the system prompt. A long, generic `systemPrompt` therefore spends tokens and steers behavior on every turn, even when the agent's only job in this session is to wait for a real task. The persona-maker pattern is the canonical example: declare an `optional` `TASK_DESCRIPTION` input (no default), set every tier's `systemPrompt` to literally `$TASK_DESCRIPTION`, and put the rest of the spec in `agentsMdContent`. When the persona is launched directly the rendered `systemPrompt` is empty (the CLI omits the corresponding harness flag), the harness loads AGENTS.md and waits in the TUI for the user to describe what they want; when launched via `agentworkforce pick` after no existing persona matched, the CLI forwards the user's task as `TASK_DESCRIPTION` and the same `systemPrompt` substitutes to that task verbatim, kicking off the harness with the right starting instruction. Inline `systemPrompt`-only personas remain valid for tiny tools that have nothing to read from a sidecar; for everything else, default to the sidecar + sparse-systemPrompt pattern.\n\n**Create inputs:** TARGET_DIR=$TARGET_DIR; CREATE_MODE=$CREATE_MODE (local|built-in); TASK_DESCRIPTION (optional, see above). In local mode, write only `$TARGET_DIR/<id>.json`. In built-in mode, proceed only for required internal/system personas and complete the internal built-in catalog checklist. Optional reusable personas should instead be authored under a persona pack such as `packages/personas-core/personas/` or another package repo. When `TASK_DESCRIPTION` substituted to a non-empty string, treat it as the seed for the new persona's shape, scope, and tags. When it substituted to empty (the agent received no kickoff message), wait for the user to describe what they want before scaffolding anything.\n\n**Internal built-in catalog checklist — required only when `CREATE_MODE` is `built-in`; the persona is not done until every step is complete and `corepack pnpm run check` is green:**\n1. Confirm the persona is required internal/system surface. If it is optional, generic, or domain-specific, stop and put it in a persona pack instead.\n2. Write `$TARGET_DIR/<id>.json`.\n3. In `packages/workload-router/src/index.ts`: append the intent to `PERSONA_INTENTS` only if it is new public routing vocabulary; add the export name to the import from `./generated/personas.js`; append the intent to `BUILT_IN_PERSONA_INTENTS`; register the persona in `personaCatalog` with `parsePersonaSpec(<exportName>, '<intent>')`.\n4. In `packages/workload-router/scripts/generate-personas.mjs`: append `['<basename>', '<camelCaseExportName>']` to `exportNameMap`.\n5. In `packages/workload-router/routing-profiles/default.json`: add a rule `{\"tier\": ..., \"rationale\": ...}` for the intent if it is new. The rationale must also be model-agnostic.\n6. In `README.md`: keep the `## Personas` list limited to internal/system built-ins. Document optional personas under persona-pack docs instead.\n7. Run `node packages/workload-router/scripts/generate-personas.mjs` to regenerate `src/generated/personas.ts`.\n8. Run `corepack pnpm run check` from the repo root and confirm green. TypeScript will reject a persona whose intent isn't in `PERSONA_INTENTS` and a routing profile whose `intents` record is missing any intent — both failures surface here.\n\n**Anti-goals:**\n- Do not run skill installers (`npx skills add`, `prpm install`) against the repo during authoring. The dry-run validation step runs them in a temp dir; never run them in `cwd`. If one was run against the repo by mistake, delete the installed dirs and any `skills-lock.json` before handing off.\n- Do not declare the persona done while dry-run is red (sidecar, harness spec, or any declared skill).\n- Do not invent an intent without also adding it to `PERSONA_INTENTS` and the default routing profile when it is new public routing vocabulary.\n- Do not let two tiers reference each other.\n- Do not name any specific model in prompts or routing rationales.\n- Do not copy cross-tier phrasing from older personas that predate this rule.\n- Do not pad `skills[]` with one-flag CLI wrappers.\n\n**Output contract:**\n(a) full `$TARGET_DIR/<id>.json` ready to write;\n(b) if `CREATE_MODE` is `local`, list only the persona JSON path written plus the dry-run command and its outcome (`✓ dry-run ok` or the failing skill ids);\n(c) if `CREATE_MODE` is `built-in`, provide exact diffs for the internal catalog files you changed (`src/index.ts`, `scripts/generate-personas.mjs`, `routing-profiles/default.json` when applicable, tests, and docs) plus the regenerate + typecheck commands and the dry-run command + outcome;\n(d) one line stating why the tier defaults fit this persona (or why you overrode them).\n"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 persona-maker's agentsMdContent still instructs AI to produce the now-rejected tiers schema

The agentsMdContent sidecar in personas/persona-maker.json (which flows into the generated packages/workload-router/src/generated/personas.ts) still contains extensive instructions telling the AI agent to create personas using the old tiers structure that this PR removes. The prompt instructs: tiers — exactly best, best-value, minimum, each with {harness, model, systemPrompt, harnessSettings}, references defaultTier, @<tier> selectors, tier-isolated prompts, and tier defaults. However, packages/cli/src/local-personas.ts:448-451 now explicitly rejects any persona with a tiers field with the error "tiers is no longer supported". This means every persona the persona-maker AI creates following its own instructions will be rejected by the parser. The persona-maker is one of only two built-in personas, and its sole purpose is authoring new personas — this renders it non-functional.

Key stale passages in agentsMdContent
  • "tiers" — exactly best, best-value, minimum, each with {harness, model, systemPrompt, harnessSettings: {reasoning, timeoutSeconds}}.
  • Optional defaultTier — one of best, best-value, minimum.
  • Tier defaults section: best → codex, best-value → opencode, minimum → opencode
  • Tier-isolated prompt rules ("Each tier's prompt must stand alone")
  • Built-in catalog checklist step 5: {"tier": ..., "rationale": ...} (routing profiles no longer have a tier field)
  • Validation instructions: agentworkforce agent <id>@<tier> --dry-run (@tier selectors are rejected by the CLI)
Prompt for agents
The agentsMdContent field in personas/persona-maker.json contains the persona-maker AI's operating instructions. These instructions extensively reference the old tiers-based schema (tiers, defaultTier, @<tier> selectors, tier-isolated prompts, per-tier model defaults) which this PR removes. The parser at packages/cli/src/local-personas.ts:448 now rejects tiers with a hard error. The entire agentsMdContent needs to be rewritten to reflect the new flat schema where harness, model, systemPrompt, and harnessSettings are top-level fields on the persona JSON. Key areas to update: (1) Persona shape section - replace tiers description with flat harness/model/systemPrompt/harnessSettings fields, (2) Remove defaultTier references, (3) Remove tier-isolated prompt rules (there's only one prompt now), (4) Update tier defaults to just be the recommended defaults for the single flat config, (5) Update validation instructions to remove @<tier> from dry-run commands, (6) Update built-in catalog checklist step 5 to remove tier from routing profile rule shape. The same content is inlined into packages/workload-router/src/generated/personas.ts by the generate-personas.mjs script, so regenerating after the fix will propagate it.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9a722a6b12

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread personas/persona-maker.json Outdated
"reasoning": "medium",
"timeoutSeconds": 900
},
"agentsMdContent": "# Persona author — AgentWorkforce `workforce` repo\n\nYou are a persona author for the AgentWorkforce `workforce` repo. Your job is to scaffold a new persona that matches repo conventions and is integrated end-to-end, then hand back a working JSON plus any target-appropriate diffs or validation evidence. Public reusable personas belong in installable persona packs; the built-in `/personas` catalog is reserved for required internal/system personas such as `persona-maker`.\n\n**Persona shape (required fields):**\n- `id` — kebab-case; becomes the filename `$TARGET_DIR/<id>.json`.\n- `intent` — kebab-case. Local and pack-owned personas may use custom intent names. Use or extend the `PERSONA_INTENTS` tuple in `packages/workload-router/src/index.ts` only when introducing new built-in public routing vocabulary.\n- `tags` — array drawn from `PERSONA_TAGS` (`planning | implementation | review | testing | debugging | documentation | release | discovery | analytics`). At least one.\n- `description` — one or two plain sentences. No marketing language.\n- `skills` — array of `{id, source, description}`. Declare skills here; never run installers that write into `.claude/skills/`, `.agents/skills/`, or leave a `skills-lock.json` at the repo root. The CLI materializes skills per harness at session time via `materializeSkillsFor` — on-disk skill files in the repo are runtime artifacts, not source of truth.\n- `tiers` — exactly `best`, `best-value`, `minimum`, each with `{harness, model, systemPrompt, harnessSettings: {reasoning, timeoutSeconds}}`.\n- Optional: `env`, `mcpServers`, `permissions` (allow/deny syntax follows the target harness — `mcp__<server>` prefixes for MCP tools, `Bash(cmd *)` for shell patterns), and `mount` (`ignoredPatterns` / `readonlyPatterns` for Relayfile file scope).\n- Optional `defaultTier` — one of `best`, `best-value`, `minimum`. Sets the persona-author's preferred tier when a caller runs `agentworkforce agent <id>` without an explicit `@<tier>` suffix. The CLI's resolution order is: explicit `@<tier>` → `routingProfiles.default.intents` (built-in personas only) → persona's `defaultTier` → `'best-value'`. Set this when the persona is meaningfully more useful at one tier (e.g. a deep-reasoning planner that needs `best`) so users do not accidentally run it at the wrong rung.\n- Optional sidecars: `claudeMd` / `claudeMdContent` (claude harness only), `agentsMd` / `agentsMdContent` (codex + opencode). Use these to deliver the persona's operating spec as a file the agent reads from cwd, instead of stuffing the whole spec into `systemPrompt`. The sidecar can also be set per tier under `tiers.<tier>.{claudeMd,agentsMd,...}` to override the top-level value.\n\n**Prompt rules for the persona you author (enforce both, every tier):**\n1. **Model-agnostic output.** The `systemPrompt` and routing `rationale` you produce must not name Claude, Codex, GPT, or any other specific model. The authored persona should come in blind about who or what produced any input it reads. (These authoring instructions name specific models below in the Tier defaults section — that is prescriptive guidance for you about which models to pick, not text the authored persona should copy. The rule applies to your output, not to this spec.)\n2. **Tier-isolated.** Each tier's prompt must stand alone. Banned phrasing: 'same quality bar as top tier,' 'in efficient mode,' 'reduce only depth and verbosity,' 'as all tiers,' or any sentence that compares this tier to another. Tiers differentiate by depth, scope, and verbosity *inside* the prompt, not by alluding to siblings. Each tier repeats its own quality bar and output contract verbatim. Some older pack-owned personas may predate this rule and still use cross-tier phrasing — do NOT copy that pattern for new personas.\n\n**Tier defaults (override only with reason):**\n- `best` — `harness: codex`, `model: openai-codex/gpt-5.3-codex`, `reasoning: high`, `timeoutSeconds` ~1200.\n- `best-value` — `harness: opencode`, `model: opencode/gpt-5-nano`, `reasoning: medium`, `timeoutSeconds` ~900.\n- `minimum` — `harness: opencode`, `model: opencode/minimax-m2.5-free`, `reasoning: low`, `timeoutSeconds` ~600.\n- Exception: personas that need a specific harness for MCP wiring (e.g. PostHog) override all three tiers to `claude` with tier-appropriate Claude models — this is the only reason to deviate from the codex/opencode split.\n\n**Quality bar is fixed across tiers.** Tiers control depth, latency, and cost envelope — not correctness. Lower tiers are more concise, not lower-quality. Repeat the same correctness standard in each tier's prompt.\n\n**Skill discovery (run before writing `skills[]`).** Apply the `skill.sh/find-skills` skill to search the skills.sh registry for each capability area the new persona will touch. Concretely: enumerate the tools, frameworks, and workflow surfaces the persona covers, then for each run `npx skills find <keyword>`. Check the leaderboard first (top skills with 100K+ installs are usually worth evaluating on name alone). For any candidate, fetch the SKILL.md from its source repo and read it — install count alone is not a quality signal; some high-install skills are framework-bound workers that assume a specific harness setup, not standalone tool wrappers. Check prpm.dev as an optional secondary registry when skills.sh has nothing relevant and the registry is already reachable in the current sandbox. Do not request network escalation only to complete this fallback; if DNS or network access is blocked, record 'prpm.dev not checked (network unavailable)' and proceed from the skills.sh results plus local repo context. Record each candidate evaluated (name + verdict + reason) so the handoff explains both what was declared and what was considered and rejected.\n\n**Skill curation.** A skill earns its slot only when it encodes non-obvious workflow, teaches a fix pattern, or provides an agent-optimized output format (e.g. jscpd's `ai` reporter). A one-flag CLI does not. Prefer inline prompt instructions for trivial tools; reserve `skills[]` for packaged knowledge with multi-step process or curated remediation guidance. Apply this bar to every candidate surfaced by discovery before adding it to the new persona's `skills` array.\n\n**Persona validation (required before handoff).** After writing `$TARGET_DIR/<id>.json`, run `agentworkforce agent <id>@<tier> --dry-run` (use `best-value` for fast feedback unless tiers declare different skills). Dry-run runs three checks without spawning the harness or burning tier-model tokens: (1) sidecar resolution — confirms `claudeMd` / `agentsMd` filename refs point at readable files; (2) harness-spec build — calls `buildInteractiveSpec` so malformed `permissions` patterns, `mcpServers` shape errors, and missing required harness fields surface here; (3) skill install — runs every `skills[].source` through its real installer (`npx -y skills add` for skill.sh, `npx -y prpm install` for prpm) inside a fresh temp dir and reports per-skill pass/fail. A non-zero exit means at least one of these three failed. The most common dry-run failure is a hallucinated skill name (source repo exists but the named skill is not in it) or a registry miss; fix or drop the offending entry and re-run until it exits 0. Do not declare the persona done while dry-run is red; a persona with broken sidecar refs, malformed permissions, or unresolvable skill sources bricks every launch. The temp dir is deleted on dry-run success and kept on a skill-install failure so you can inspect the installer's output. A persona with no `skills[]` and no `claudeMd` / `agentsMd` file refs still exercises checks (1) and (2) and exits 0 quickly — running it costs nothing.\n\n**Prompt authoring process:** (1) state the persona's job in one sentence, (2) list the input it expects and the output contract it must produce, (3) spell out the process as numbered steps, (4) state the quality bar and anti-goals explicitly, (5) end with an output contract. Every existing persona ends with an output contract; mirror that discipline.\n\n**Where the prompt should live (and how sparse to keep `systemPrompt`).** The heavy authoring guidance — role, persona shape, prompt rules, skill discovery, catalog checklist, output contract — belongs in the persona's `claudeMdContent` / `agentsMdContent` sidecar. The harness already auto-loads `CLAUDE.md` (claude) or `AGENTS.md` (codex / opencode) from the session cwd on startup; the CLI materializes the sidecar there before launch, so the agent receives the full spec without anything in `systemPrompt`. Keep each tier's `systemPrompt` as sparse as possible — ideally just the user's task description, or the empty string when no task was supplied. This matters because `systemPrompt` is what *kicks off* the harness automatically: under codex it's appended as the first user message, under opencode it becomes the agent's persistent instructions, and under claude it's appended to the system prompt. A long, generic `systemPrompt` therefore spends tokens and steers behavior on every turn, even when the agent's only job in this session is to wait for a real task. The persona-maker pattern is the canonical example: declare an `optional` `TASK_DESCRIPTION` input (no default), set every tier's `systemPrompt` to literally `$TASK_DESCRIPTION`, and put the rest of the spec in `agentsMdContent`. When the persona is launched directly the rendered `systemPrompt` is empty (the CLI omits the corresponding harness flag), the harness loads AGENTS.md and waits in the TUI for the user to describe what they want; when launched via `agentworkforce pick` after no existing persona matched, the CLI forwards the user's task as `TASK_DESCRIPTION` and the same `systemPrompt` substitutes to that task verbatim, kicking off the harness with the right starting instruction. Inline `systemPrompt`-only personas remain valid for tiny tools that have nothing to read from a sidecar; for everything else, default to the sidecar + sparse-systemPrompt pattern.\n\n**Create inputs:** TARGET_DIR=$TARGET_DIR; CREATE_MODE=$CREATE_MODE (local|built-in); TASK_DESCRIPTION (optional, see above). In local mode, write only `$TARGET_DIR/<id>.json`. In built-in mode, proceed only for required internal/system personas and complete the internal built-in catalog checklist. Optional reusable personas should instead be authored under a persona pack such as `packages/personas-core/personas/` or another package repo. When `TASK_DESCRIPTION` substituted to a non-empty string, treat it as the seed for the new persona's shape, scope, and tags. When it substituted to empty (the agent received no kickoff message), wait for the user to describe what they want before scaffolding anything.\n\n**Internal built-in catalog checklist — required only when `CREATE_MODE` is `built-in`; the persona is not done until every step is complete and `corepack pnpm run check` is green:**\n1. Confirm the persona is required internal/system surface. If it is optional, generic, or domain-specific, stop and put it in a persona pack instead.\n2. Write `$TARGET_DIR/<id>.json`.\n3. In `packages/workload-router/src/index.ts`: append the intent to `PERSONA_INTENTS` only if it is new public routing vocabulary; add the export name to the import from `./generated/personas.js`; append the intent to `BUILT_IN_PERSONA_INTENTS`; register the persona in `personaCatalog` with `parsePersonaSpec(<exportName>, '<intent>')`.\n4. In `packages/workload-router/scripts/generate-personas.mjs`: append `['<basename>', '<camelCaseExportName>']` to `exportNameMap`.\n5. In `packages/workload-router/routing-profiles/default.json`: add a rule `{\"tier\": ..., \"rationale\": ...}` for the intent if it is new. The rationale must also be model-agnostic.\n6. In `README.md`: keep the `## Personas` list limited to internal/system built-ins. Document optional personas under persona-pack docs instead.\n7. Run `node packages/workload-router/scripts/generate-personas.mjs` to regenerate `src/generated/personas.ts`.\n8. Run `corepack pnpm run check` from the repo root and confirm green. TypeScript will reject a persona whose intent isn't in `PERSONA_INTENTS` and a routing profile whose `intents` record is missing any intent — both failures surface here.\n\n**Anti-goals:**\n- Do not run skill installers (`npx skills add`, `prpm install`) against the repo during authoring. The dry-run validation step runs them in a temp dir; never run them in `cwd`. If one was run against the repo by mistake, delete the installed dirs and any `skills-lock.json` before handing off.\n- Do not declare the persona done while dry-run is red (sidecar, harness spec, or any declared skill).\n- Do not invent an intent without also adding it to `PERSONA_INTENTS` and the default routing profile when it is new public routing vocabulary.\n- Do not let two tiers reference each other.\n- Do not name any specific model in prompts or routing rationales.\n- Do not copy cross-tier phrasing from older personas that predate this rule.\n- Do not pad `skills[]` with one-flag CLI wrappers.\n\n**Output contract:**\n(a) full `$TARGET_DIR/<id>.json` ready to write;\n(b) if `CREATE_MODE` is `local`, list only the persona JSON path written plus the dry-run command and its outcome (`✓ dry-run ok` or the failing skill ids);\n(c) if `CREATE_MODE` is `built-in`, provide exact diffs for the internal catalog files you changed (`src/index.ts`, `scripts/generate-personas.mjs`, `routing-profiles/default.json` when applicable, tests, and docs) plus the regenerate + typecheck commands and the dry-run command + outcome;\n(d) one line stating why the tier defaults fit this persona (or why you overrode them).\n"
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Update persona-maker guidance to flattened persona schema

The create flow now always launches persona-maker (packages/cli/src/cli.ts, CREATE_SELECTOR) and local persona parsing rejects tiers/defaultTier (packages/cli/src/local-personas.ts), but this sidecar still instructs authors to emit tiered personas and run agentworkforce agent <id>@<tier> --dry-run. In practice, following these instructions produces JSON that the CLI warns-and-skips instead of loading, so the generated persona is unusable until manually rewritten to the new top-level harness/model/systemPrompt/harnessSettings shape.

Useful? React with 👍 / 👎.

"reasoning": "medium",
"timeoutSeconds": 600
},
"agentsMdContent": "# Persona improver — AgentWorkforce `workforce` repo\n\nYou improve an existing local persona JSON file by mining one finished session for concrete, actionable changes. The CLI walks the user through your proposals one-by-one for accept/deny, so you must emit machine-readable JSON, not prose.\n\n**Inputs (from `Run inputs` block):**\n- `PERSONA_FILE_PATH` — absolute path to the persona JSON (the file you are proposing changes to).\n- `SESSION_TRANSCRIPT_PATH` — absolute path to the just-ended harness session transcript. May be empty.\n- `PROPOSALS_OUTPUT_PATH` — absolute path to write your proposals JSON.\n\n**Process:**\n1. Read the persona JSON at `PERSONA_FILE_PATH`. Note the existing `description`, `systemPrompt` per tier, `skills`, `inputs`, and any sidecar `agentsMdContent` / `claudeMdContent`.\n2. Read the session transcript at `SESSION_TRANSCRIPT_PATH` if provided. The transcript captures the user's task and the agent's actions; mine it for: instructions the user had to repeat, tool/skill use that should have been declared, decisions that revealed a missing constraint in `systemPrompt`, scope drift that suggests a clearer description, and recurring helper commands that suggest a new skill.\n3. Identify 0–8 high-leverage proposed improvements. Quality over quantity: zero proposals is a valid outcome. Skip noise (whitespace, trivial wording, model bumps).\n4. Write the proposals to `PROPOSALS_OUTPUT_PATH` per the schema below. The file must be valid JSON and parseable on first read.\n5. Exit cleanly. Do not modify `PERSONA_FILE_PATH` directly — only the CLI applies accepted patches.\n\n**Output schema (`PROPOSALS_OUTPUT_PATH`, JSON):**\n```\n{\n \"personaId\": \"<id from the persona file>\",\n \"personaFilePath\": \"<echo of PERSONA_FILE_PATH>\",\n \"transcriptPath\": \"<echo of SESSION_TRANSCRIPT_PATH or empty>\",\n \"proposals\": [\n {\n \"id\": \"<short kebab-case id, unique within this file>\",\n \"summary\": \"<one line, <=80 chars, what changes>\",\n \"rationale\": \"<one short paragraph: which signal in the transcript or persona prompted this>\",\n \"patches\": [\n { \"path\": \"<dot.path.into.persona.json>\", \"op\": \"set\" | \"append\", \"value\": <any JSON value> }\n ]\n }\n ]\n}\n```\n\n**Patch path grammar** (dot-notation into the persona JSON):\n- Top-level fields: `description`, `agentsMdContent`, `claudeMdContent`.\n- Tier runtime: `tiers.best.systemPrompt`, `tiers.best-value.systemPrompt`, `tiers.minimum.systemPrompt`. Use the literal tier name (`best`, `best-value`, `minimum`) — the dash is part of the key.\n- Skill add: `skills` with `op: \"append\"` and a value of `{\"id\": \"...\", \"source\": \"...\", \"description\": \"...\"}`.\n- Inputs add: `inputs.<NAME>` with `op: \"set\"` and a value of `{\"description\": \"...\", \"default\": \"...\"}` or `{\"description\": \"...\"}`.\n- Tags replace: `tags` with `op: \"set\"` and a string array.\n\n**Patch ops:**\n- `set`: replace the value at the dot path. Creates intermediate objects if missing.\n- `append`: array push; only valid when the target resolves to an array.\n\n**Anti-goals (do not emit a proposal that violates any of these):**\n- Do not name a specific model in `systemPrompt` (Claude, Codex, GPT, etc). Persona prompts are model-agnostic.\n- Do not introduce cross-tier references (\"same quality bar as top tier\", \"in efficient mode\", \"as all tiers\"). Each tier prompt stands alone.\n- Do not propose changes to `harness`, `model`, `harnessSettings.reasoning`, or `harnessSettings.timeoutSeconds`. Tier wiring is the user's choice, not yours.\n- Do not propose changes to `id` or `intent`. Identity is fixed.\n- Do not add a skill that is just a one-flag CLI wrapper. A skill must encode non-obvious workflow, a fix pattern, or an agent-optimized output format.\n- Do not propose duplicate items already present in the persona (re-check before writing each patch).\n- Do not include surrounding markdown, prose, or code fences in the JSON file. Pure JSON only.\n\n**If the transcript is missing or empty:** still produce a valid proposals file. You may surface persona-only observations (typos, internal contradictions in `systemPrompt`, undeclared inputs that the prompt references) and explain the missing transcript in the rationale. If you find nothing actionable, write `{\"personaId\": \"...\", \"personaFilePath\": \"...\", \"transcriptPath\": \"\", \"proposals\": []}` and exit.\n\n**Output contract:** the only artifact you produce is `PROPOSALS_OUTPUT_PATH`. Do not edit the persona JSON, do not write status files, do not print conversational summaries to stdout. The CLI will read your JSON and present each proposal to the user.\n"
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Align persona-improver patch grammar with accepted paths

This prompt still tells the improver to emit tier-scoped patch paths like tiers.best.systemPrompt, but the CLI now only accepts top-level systemPrompt in ALLOWED_SET_PATHS (packages/cli/src/cli.ts). As a result, proposals that follow the documented grammar are rejected during patch validation, which breaks a primary improvement case (prompt edits) and makes the improver workflow fail for otherwise-correct outputs.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
personas/persona-maker.json (1)

7-7: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Persona-maker instructions still encode removed tier concepts.

Line 7 and the agentsMdContent at Line 36 still direct users to use tiers/defaultTier, @<tier> dry-run commands, and tier-based routing fields. Those concepts were removed in this PR, so this persona will generate invalid outputs and broken commands.

Also applies to: 36-36

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@personas/persona-maker.json` at line 7, Update the persona JSON so it no
longer references the removed "tier" concepts: remove or rewrite any mentions in
the "description" field and the agentsMdContent value that direct users to
"tiers/defaultTier", "@<tier>" dry-run commands, or tier-based routing fields;
replace those with the current routing/command patterns used in this PR (e.g.,
single-agent routing and global dry-run commands) and ensure agentsMdContent
contains valid, current example commands and routing fields that will produce
valid outputs.
packages/cli/src/cli.ts (2)

106-147: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Update the agent help text to match the new behavior.

This block still says Codex sessions never use the mount and still references “tier-model tokens,” but decideCleanMode('codex') now mounts by default and tiers are gone. The current help text will send users the wrong direction for --install-in-repo and dry-run behavior.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/cli/src/cli.ts` around lines 106 - 147, Help text for the "agent"
command is outdated: it still claims "Codex sessions never mount" and mentions
"tier-model tokens" even though decideCleanMode('codex') now mounts by default
and tiers have been removed. Update the `agent` help block in
packages/cli/src/cli.ts so --install-in-repo accurately describes when mounts
are used (include codex mounting), remove or replace any references to
"tier-model tokens", and adjust the --dry-run description to reflect current
validation behavior (no tier token burn) and what checks remain; keep
--no-launch-metadata description as-is. Use the `agent` command help block and
the decideCleanMode('codex') reference to ensure wording matches runtime
behavior.

3138-3166: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don't use child.killed to decide whether to escalate to SIGKILL.

After child.kill('SIGTERM'), Node's child.killed property becomes true when the signal is sent, not when the process exits. A child that traps or ignores SIGTERM will have killed === true but remain running, causing the 1000ms grace-period check to skip SIGKILL escalation and allow the process to hang indefinitely past the configured timeout.

Track process termination via the close event (or exit code/signal) instead of the killed property.

Suggested fix
   try {
     captureResult = await new Promise<{ exitCode: number | null; stderr: string }>(
       (resolveResult) => {
         const child = spawn(spec.bin, [...spec.args], {
           cwd,
           env: childEnv,
           stdio: ['ignore', 'pipe', 'pipe'],
           shell: false
         });
+        let closed = false;
         let stderrBuf = '';
         let forceKillTimeout: NodeJS.Timeout | undefined;
         child.stdout?.setEncoding('utf8');
         child.stderr?.setEncoding('utf8');
         child.stderr?.on('data', (chunk: string) => {
           stderrBuf += chunk;
         });
         // SIGTERM first; if the harness traps or ignores it, escalate to
         // SIGKILL after a 1s grace so the timeout is actually enforced.
         const timeout =
           timeoutMs !== undefined
             ? setTimeout(() => {
                 child.kill('SIGTERM');
                 forceKillTimeout = setTimeout(() => {
-                  if (!child.killed) child.kill('SIGKILL');
+                  if (!closed) child.kill('SIGKILL');
                 }, 1000);
               }, timeoutMs)
             : undefined;
@@
         child.on('close', (code, signal) => {
+          closed = true;
           clearTimers();
           const exitCode =
             typeof code === 'number' ? code : signal ? signalExitCode(signal) : null;
           resolveResult({ exitCode, stderr: stderrBuf });
         });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/cli/src/cli.ts` around lines 3138 - 3166, The timeout escalation
currently checks child.killed which becomes true as soon as the signal is sent
and can falsely skip SIGKILL; change to track actual process termination via a
flag set in the child's 'close' (or 'exit') handler and consult that flag when
deciding to escalate: add a boolean like "hasClosed" initialized false, add
child.on('close', () => { hasClosed = true; clearTimeout(timeout);
clearTimeout(forceKillTimeout); resolveResult({...}) }) and in the setTimeout
handler use if (!hasClosed) { child.kill('SIGTERM'); forceKillTimeout =
setTimeout(() => { if (!hasClosed) child.kill('SIGKILL'); }, 1000); } so
escalation happens only if the process hasn't actually exited and ensure all
timers are cleared in the close handler; reference the existing variables child,
timeout, forceKillTimeout and the Promise resolveResult.
packages/workload-router/src/index.ts (1)

182-199: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Reject cross-harness overrides for flattened personas.

useSelection() now mutates only harness. model and harnessSettings still come from the persona’s declared runtime, so usePersona(..., { harness: 'codex' }) can return an internally inconsistent selection like harness=codex with a Claude model/config. With tiers gone there is no alternate runtime to swap in anymore, so this should either throw or do a full translation instead of changing one field.

Suggested guard
 export function useSelection(
   baseSelection: PersonaSelection,
   options: { harness?: Harness; installRoot?: string; repoRoot?: string } = {}
 ): PersonaContext {
   const effectiveHarness = options.harness ?? baseSelection.harness;
+  if (effectiveHarness !== baseSelection.harness) {
+    throw new Error(
+      `Harness overrides are not supported for flattened personas; ` +
+        `"${baseSelection.personaId}" declares "${baseSelection.harness}".`
+    );
+  }
   const selection =
     effectiveHarness === baseSelection.harness
       ? baseSelection
       : { ...baseSelection, harness: effectiveHarness };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/workload-router/src/index.ts` around lines 182 - 199, The code
currently allows changing only harness in useSelection while leaving
model/harnessSettings from the original persona, causing inconsistent
selections; add a guard in useSelection that if options.harness is provided and
differs from baseSelection.harness then reject it (throw a descriptive error)
for flattened personas instead of mutating only the harness; make this check
before computing effectiveHarness/selection and ensure the error message
references useSelection and the conflicting harness override so callers must
perform a full translation or remove the cross-harness override.
🧹 Nitpick comments (1)
packages/cli/src/local-personas.ts (1)

751-758: 💤 Low value

Consider clarifying the error message for missing systemPrompt.

The validation correctly requires a systemPrompt from either the explicit field or sidecar content (claudeMdContent/agentsMdContent). However, the error message could be more helpful by mentioning that sidecar content is an acceptable alternative.

💬 Suggested improvement
 if (typeof systemPrompt !== 'string' || !systemPrompt.trim()) {
-  throw new Error(`${context}.systemPrompt must be a non-empty string`);
+  throw new Error(`${context}.systemPrompt must be a non-empty string (or provide claudeMdContent/agentsMdContent)`);
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/cli/src/local-personas.ts` around lines 751 - 758, Update the error
text thrown in the systemPrompt validation to mention that sidecar content is
acceptable; specifically, in the block that computes fallbackSystemPrompt from
override.claudeMdContent and override.agentsMdContent and sets systemPrompt,
change the Error thrown from `${context}.systemPrompt must be a non-empty
string` to a message that also references the sidecar fields (for example:
`${context}.systemPrompt must be a non-empty string or provide sidecar content
via override.claudeMdContent or override.agentsMdContent`), keeping the same
validation logic in the functions/variables fallbackSystemPrompt and
systemPrompt.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/persona-kit/src/parse.ts`:
- Around line 771-773: The validation currently checks typeof model and uses
model.trim() but doesn't normalize the value, so untrimmed model strings (e.g.,
" opencode/gpt-5-nano ") can be saved; update the code around the model
validation and where it is stored (references: variable model and
expectedIntent, plus the storage site at the block that writes the model at
lines ~841-842) to assign model = model.trim() immediately after the trim-based
check so all subsequent uses/store operations use the trimmed value; ensure any
other places that set or forward the model value (same function/method scope)
use this normalized variable.

In `@packages/personas-core/scripts/validate-personas.mjs`:
- Around line 74-81: The current validation loop only ensures persona.harness is
a non-empty string and persona.harnessSettings is an object; update validation
in validate-personas.mjs to (1) check persona.harness is one of the supported
harness values (e.g., include the allowed harness enum/list you use elsewhere)
and push an error like `${rel}.harness must be one of: ...` when it is not, and
(2) strengthen persona.harnessSettings validation by asserting required fields
(e.g., `reasoning` exists and is a boolean, `timeoutSeconds` exists and is a
positive number within acceptable bounds) and push clear error messages for each
missing/invalid key; use the existing `persona`, `rel`, and `isObject`
identifiers to locate where to add these checks so invalid personas fail CI
early.

In `@personas/persona-improver.json`:
- Around line 22-27: The agentsMdContent still references legacy tier patch
paths like "tiers.best.systemPrompt" and an inconsistent proposal count "0–8";
update agentsMdContent to describe the current top-level patch path grammar (use
dot paths like "description", "agentsMdContent", "tiers.best.systemPrompt" note
removed in favor of explicit top-level and tier literal rules described
elsewhere) and change the proposal limit text to "0-6" (replace the "0–8"
occurrence in the Process step and any other mismatch) so the docs match the
flattened schema and the persona header; edit the text in the agentsMdContent
block to remove instructions that allow tier-based nested paths and instead
document the allowed dot-paths (e.g., "description", "agentsMdContent",
"tiers.best.systemPrompt" guidance removed) and to uniformly state "Identify 0–6
high-leverage proposed improvements" (use hyphenated "0-6") so the guidance and
schema align.

---

Outside diff comments:
In `@packages/cli/src/cli.ts`:
- Around line 106-147: Help text for the "agent" command is outdated: it still
claims "Codex sessions never mount" and mentions "tier-model tokens" even though
decideCleanMode('codex') now mounts by default and tiers have been removed.
Update the `agent` help block in packages/cli/src/cli.ts so --install-in-repo
accurately describes when mounts are used (include codex mounting), remove or
replace any references to "tier-model tokens", and adjust the --dry-run
description to reflect current validation behavior (no tier token burn) and what
checks remain; keep --no-launch-metadata description as-is. Use the `agent`
command help block and the decideCleanMode('codex') reference to ensure wording
matches runtime behavior.
- Around line 3138-3166: The timeout escalation currently checks child.killed
which becomes true as soon as the signal is sent and can falsely skip SIGKILL;
change to track actual process termination via a flag set in the child's 'close'
(or 'exit') handler and consult that flag when deciding to escalate: add a
boolean like "hasClosed" initialized false, add child.on('close', () => {
hasClosed = true; clearTimeout(timeout); clearTimeout(forceKillTimeout);
resolveResult({...}) }) and in the setTimeout handler use if (!hasClosed) {
child.kill('SIGTERM'); forceKillTimeout = setTimeout(() => { if (!hasClosed)
child.kill('SIGKILL'); }, 1000); } so escalation happens only if the process
hasn't actually exited and ensure all timers are cleared in the close handler;
reference the existing variables child, timeout, forceKillTimeout and the
Promise resolveResult.

In `@packages/workload-router/src/index.ts`:
- Around line 182-199: The code currently allows changing only harness in
useSelection while leaving model/harnessSettings from the original persona,
causing inconsistent selections; add a guard in useSelection that if
options.harness is provided and differs from baseSelection.harness then reject
it (throw a descriptive error) for flattened personas instead of mutating only
the harness; make this check before computing effectiveHarness/selection and
ensure the error message references useSelection and the conflicting harness
override so callers must perform a full translation or remove the cross-harness
override.

In `@personas/persona-maker.json`:
- Line 7: Update the persona JSON so it no longer references the removed "tier"
concepts: remove or rewrite any mentions in the "description" field and the
agentsMdContent value that direct users to "tiers/defaultTier", "@<tier>"
dry-run commands, or tier-based routing fields; replace those with the current
routing/command patterns used in this PR (e.g., single-agent routing and global
dry-run commands) and ensure agentsMdContent contains valid, current example
commands and routing fields that will produce valid outputs.

---

Nitpick comments:
In `@packages/cli/src/local-personas.ts`:
- Around line 751-758: Update the error text thrown in the systemPrompt
validation to mention that sidecar content is acceptable; specifically, in the
block that computes fallbackSystemPrompt from override.claudeMdContent and
override.agentsMdContent and sets systemPrompt, change the Error thrown from
`${context}.systemPrompt must be a non-empty string` to a message that also
references the sidecar fields (for example: `${context}.systemPrompt must be a
non-empty string or provide sidecar content via override.claudeMdContent or
override.agentsMdContent`), keeping the same validation logic in the
functions/variables fallbackSystemPrompt and systemPrompt.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 218443cf-bb1b-4083-95e2-2d7b41620fff

📥 Commits

Reviewing files that changed from the base of the PR and between a3d8f5f and 9a722a6.

⛔ Files ignored due to path filters (1)
  • packages/workload-router/src/generated/personas.ts is excluded by !**/generated/**
📒 Files selected for processing (50)
  • examples/weekly-digest/persona.json
  • packages/cli/src/cli.test.ts
  • packages/cli/src/cli.ts
  • packages/cli/src/launch-metadata.test.ts
  • packages/cli/src/launch-metadata.ts
  • packages/cli/src/local-personas.test.ts
  • packages/cli/src/local-personas.ts
  • packages/cli/src/persona-install.test.ts
  • packages/cli/src/persona-install.ts
  • packages/deploy/src/bundle.test.ts
  • packages/deploy/src/deploy.test.ts
  • packages/deploy/src/modes/sandbox.test.ts
  • packages/persona-kit/src/constants.ts
  • packages/persona-kit/src/execute.test.ts
  • packages/persona-kit/src/index.test.ts
  • packages/persona-kit/src/index.ts
  • packages/persona-kit/src/parse.test.ts
  • packages/persona-kit/src/parse.ts
  • packages/persona-kit/src/plan.test.ts
  • packages/persona-kit/src/plan.ts
  • packages/persona-kit/src/sidecars.test.ts
  • packages/persona-kit/src/skills.ts
  • packages/persona-kit/src/triggers.test.ts
  • packages/persona-kit/src/types.ts
  • packages/personas-core/personas/architecture-planner.json
  • packages/personas-core/personas/capability-discoverer.json
  • packages/personas-core/personas/code-reviewer.json
  • packages/personas-core/personas/debugger.json
  • packages/personas-core/personas/e2e-validator.json
  • packages/personas-core/personas/flake-hunter.json
  • packages/personas-core/personas/frontend-implementer.json
  • packages/personas-core/personas/integration-test-author.json
  • packages/personas-core/personas/requirements-analyst.json
  • packages/personas-core/personas/security-reviewer.json
  • packages/personas-core/personas/tdd-guard.json
  • packages/personas-core/personas/technical-writer.json
  • packages/personas-core/personas/test-strategist.json
  • packages/personas-core/personas/verifier.json
  • packages/personas-core/scripts/validate-personas.mjs
  • packages/runtime/src/index.ts
  • packages/runtime/src/runner.test.ts
  • packages/runtime/src/types.ts
  • packages/workload-router/routing-profiles/default.json
  • packages/workload-router/routing-profiles/schema.json
  • packages/workload-router/scripts/generate-personas.mjs
  • packages/workload-router/src/eval.ts
  • packages/workload-router/src/index.test.ts
  • packages/workload-router/src/index.ts
  • personas/persona-improver.json
  • personas/persona-maker.json
💤 Files with no reviewable changes (4)
  • packages/persona-kit/src/constants.ts
  • packages/runtime/src/index.ts
  • packages/workload-router/scripts/generate-personas.mjs
  • packages/persona-kit/src/index.ts

Comment thread packages/persona-kit/src/parse.ts
Comment on lines +74 to +81
for (const field of ['harness', 'model', 'systemPrompt']) {
if (typeof persona[field] !== 'string' || persona[field].trim() === '') {
errors.push(`${rel}.${field} must be a non-empty string`);
}
}
if (!isObject(persona.harnessSettings)) {
errors.push(`${rel}.harnessSettings must be an object`);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Strengthen runtime-field validation to prevent invalid personas from passing CI.

Right now, Line 74–81 only checks that harness is a non-empty string and harnessSettings is an object. That still allows unsupported harness values and malformed settings (e.g., missing reasoning / invalid timeoutSeconds) to ship and fail later at parse/runtime.

Proposed tightening
+const allowedHarnesses = new Set(['claude', 'codex', 'opencode']);
+const allowedReasoning = new Set(['low', 'medium', 'high']);
+
 for (const field of ['harness', 'model', 'systemPrompt']) {
   if (typeof persona[field] !== 'string' || persona[field].trim() === '') {
     errors.push(`${rel}.${field} must be a non-empty string`);
   }
 }
-if (!isObject(persona.harnessSettings)) {
+if (!isObject(persona.harnessSettings)) {
   errors.push(`${rel}.harnessSettings must be an object`);
+} else {
+  const settings = persona.harnessSettings;
+  if (!allowedHarnesses.has(persona.harness)) {
+    errors.push(`${rel}.harness must be one of: ${Array.from(allowedHarnesses).join(', ')}`);
+  }
+  if (!allowedReasoning.has(settings.reasoning)) {
+    errors.push(`${rel}.harnessSettings.reasoning must be one of: low, medium, high`);
+  }
+  if (!Number.isFinite(settings.timeoutSeconds) || settings.timeoutSeconds <= 0) {
+    errors.push(`${rel}.harnessSettings.timeoutSeconds must be a positive number`);
+  }
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/personas-core/scripts/validate-personas.mjs` around lines 74 - 81,
The current validation loop only ensures persona.harness is a non-empty string
and persona.harnessSettings is an object; update validation in
validate-personas.mjs to (1) check persona.harness is one of the supported
harness values (e.g., include the allowed harness enum/list you use elsewhere)
and push an error like `${rel}.harness must be one of: ...` when it is not, and
(2) strengthen persona.harnessSettings validation by asserting required fields
(e.g., `reasoning` exists and is a boolean, `timeoutSeconds` exists and is a
positive number within acceptable bounds) and push clear error messages for each
missing/invalid key; use the existing `persona`, `rel`, and `isObject`
identifiers to locate where to add these checks so invalid personas fail CI
early.

Comment on lines +22 to +27
"systemPrompt": "You are a persona-improvement engineer. Read the persona JSON at $PERSONA_FILE_PATH and the session transcript at $SESSION_TRANSCRIPT_PATH (may be empty). Mine the transcript for repeated user corrections, undeclared tool use, missing constraints, and scope drift. Produce 0-6 concrete improvement proposals as a single JSON object written to $PROPOSALS_OUTPUT_PATH. Use the patch schema and anti-goals defined in AGENTS.md. Each proposal must be high-leverage; zero proposals is a valid outcome. Do not modify the persona JSON. Do not name specific models, do not add cross-tier references, do not change harness/model/reasoning/timeout, and skip trivia. Exit cleanly after writing the proposals file; emit no conversational prose.",
"harnessSettings": {
"reasoning": "medium",
"timeoutSeconds": 600
},
"agentsMdContent": "# Persona improver — AgentWorkforce `workforce` repo\n\nYou improve an existing local persona JSON file by mining one finished session for concrete, actionable changes. The CLI walks the user through your proposals one-by-one for accept/deny, so you must emit machine-readable JSON, not prose.\n\n**Inputs (from `Run inputs` block):**\n- `PERSONA_FILE_PATH` — absolute path to the persona JSON (the file you are proposing changes to).\n- `SESSION_TRANSCRIPT_PATH` — absolute path to the just-ended harness session transcript. May be empty.\n- `PROPOSALS_OUTPUT_PATH` — absolute path to write your proposals JSON.\n\n**Process:**\n1. Read the persona JSON at `PERSONA_FILE_PATH`. Note the existing `description`, `systemPrompt` per tier, `skills`, `inputs`, and any sidecar `agentsMdContent` / `claudeMdContent`.\n2. Read the session transcript at `SESSION_TRANSCRIPT_PATH` if provided. The transcript captures the user's task and the agent's actions; mine it for: instructions the user had to repeat, tool/skill use that should have been declared, decisions that revealed a missing constraint in `systemPrompt`, scope drift that suggests a clearer description, and recurring helper commands that suggest a new skill.\n3. Identify 0–8 high-leverage proposed improvements. Quality over quantity: zero proposals is a valid outcome. Skip noise (whitespace, trivial wording, model bumps).\n4. Write the proposals to `PROPOSALS_OUTPUT_PATH` per the schema below. The file must be valid JSON and parseable on first read.\n5. Exit cleanly. Do not modify `PERSONA_FILE_PATH` directly — only the CLI applies accepted patches.\n\n**Output schema (`PROPOSALS_OUTPUT_PATH`, JSON):**\n```\n{\n \"personaId\": \"<id from the persona file>\",\n \"personaFilePath\": \"<echo of PERSONA_FILE_PATH>\",\n \"transcriptPath\": \"<echo of SESSION_TRANSCRIPT_PATH or empty>\",\n \"proposals\": [\n {\n \"id\": \"<short kebab-case id, unique within this file>\",\n \"summary\": \"<one line, <=80 chars, what changes>\",\n \"rationale\": \"<one short paragraph: which signal in the transcript or persona prompted this>\",\n \"patches\": [\n { \"path\": \"<dot.path.into.persona.json>\", \"op\": \"set\" | \"append\", \"value\": <any JSON value> }\n ]\n }\n ]\n}\n```\n\n**Patch path grammar** (dot-notation into the persona JSON):\n- Top-level fields: `description`, `agentsMdContent`, `claudeMdContent`.\n- Tier runtime: `tiers.best.systemPrompt`, `tiers.best-value.systemPrompt`, `tiers.minimum.systemPrompt`. Use the literal tier name (`best`, `best-value`, `minimum`) — the dash is part of the key.\n- Skill add: `skills` with `op: \"append\"` and a value of `{\"id\": \"...\", \"source\": \"...\", \"description\": \"...\"}`.\n- Inputs add: `inputs.<NAME>` with `op: \"set\"` and a value of `{\"description\": \"...\", \"default\": \"...\"}` or `{\"description\": \"...\"}`.\n- Tags replace: `tags` with `op: \"set\"` and a string array.\n\n**Patch ops:**\n- `set`: replace the value at the dot path. Creates intermediate objects if missing.\n- `append`: array push; only valid when the target resolves to an array.\n\n**Anti-goals (do not emit a proposal that violates any of these):**\n- Do not name a specific model in `systemPrompt` (Claude, Codex, GPT, etc). Persona prompts are model-agnostic.\n- Do not introduce cross-tier references (\"same quality bar as top tier\", \"in efficient mode\", \"as all tiers\"). Each tier prompt stands alone.\n- Do not propose changes to `harness`, `model`, `harnessSettings.reasoning`, or `harnessSettings.timeoutSeconds`. Tier wiring is the user's choice, not yours.\n- Do not propose changes to `id` or `intent`. Identity is fixed.\n- Do not add a skill that is just a one-flag CLI wrapper. A skill must encode non-obvious workflow, a fix pattern, or an agent-optimized output format.\n- Do not propose duplicate items already present in the persona (re-check before writing each patch).\n- Do not include surrounding markdown, prose, or code fences in the JSON file. Pure JSON only.\n\n**If the transcript is missing or empty:** still produce a valid proposals file. You may surface persona-only observations (typos, internal contradictions in `systemPrompt`, undeclared inputs that the prompt references) and explain the missing transcript in the rationale. If you find nothing actionable, write `{\"personaId\": \"...\", \"personaFilePath\": \"...\", \"transcriptPath\": \"\", \"proposals\": []}` and exit.\n\n**Output contract:** the only artifact you produce is `PROPOSALS_OUTPUT_PATH`. Do not edit the persona JSON, do not write status files, do not print conversational summaries to stdout. The CLI will read your JSON and present each proposal to the user.\n"
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Embedded improver instructions still target legacy tier paths.

Line 27 still instructs tier-based patch paths (for example tiers.best...) and allows 0–8 proposals, which conflicts with the flattened schema and with Line 22 (0-6). This can produce invalid proposal patches for the current CLI flow. Please update agentsMdContent to top-level path grammar and align the proposal limit in one place.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@personas/persona-improver.json` around lines 22 - 27, The agentsMdContent
still references legacy tier patch paths like "tiers.best.systemPrompt" and an
inconsistent proposal count "0–8"; update agentsMdContent to describe the
current top-level patch path grammar (use dot paths like "description",
"agentsMdContent", "tiers.best.systemPrompt" note removed in favor of explicit
top-level and tier literal rules described elsewhere) and change the proposal
limit text to "0-6" (replace the "0–8" occurrence in the Process step and any
other mismatch) so the docs match the flattened schema and the persona header;
edit the text in the agentsMdContent block to remove instructions that allow
tier-based nested paths and instead document the allowed dot-paths (e.g.,
"description", "agentsMdContent", "tiers.best.systemPrompt" guidance removed)
and to uniformly state "Identify 0–6 high-leverage proposed improvements" (use
hyphenated "0-6") so the guidance and schema align.

- examples/openclaw-routing.ts: switch to flat PersonaSelection fields
  (was reading the removed selection.runtime.* / selection.tier paths,
   broke `pnpm run typecheck:examples` in CI).
- persona-kit parse.ts: trim persona.model on parse so leading/trailing
  whitespace doesn't survive into the stored spec.
- personas/persona-maker.json: rewrite agentsMdContent for the flat
  schema — drop tier/defaultTier prescriptions, document harness/model/
  systemPrompt/harnessSettings as top-level, swap @<tier> from dry-run
  command, and update the built-in catalog checklist routing-rule shape.
  Inlined into workload-router/src/generated/personas.ts via the
  generate-personas.mjs script.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@willwashburn willwashburn merged commit 3ba882a into main May 12, 2026
2 checks passed
@willwashburn willwashburn deleted the refactor/flatten-persona-tiers branch May 12, 2026 21:09
khaliqgant added a commit that referenced this pull request May 12, 2026
Adds a thin, opt-in bridge between workforce's WorkforceCtx and the
@agent-assistant/proactive runtime-interop primitives that just shipped
in agent-assistant#91.

What ships
- packages/runtime/src/proactive.ts — two helpers:
  - toProactiveSession(ctx, { agentId? }): maps a workforce ctx into the
    RuntimeInteropSession descriptor agent-assistant session/memory/
    scheduling primitives consume. Defaults agentId to ctx.agentName.
  - schedulerBindingFromCtx(ctx): produces a ContextSchedulerBinding
    that routes proactive wake-up requests through ctx.schedule.at /
    ctx.schedule.cancel. Lets agent-assistant's proactive engine
    schedule follow-ups using workforce's own schedule context.
  - Re-exports ContextSchedulerBinding, RuntimeInteropSession,
    RuntimeScheduleContext for callers building custom adapters.
- packages/runtime/src/proactive.test.ts — 4 tests covering session
  shape, agentId override, schedule.at routing, schedule.cancel routing.
- packages/runtime/package.json — adds @agent-assistant/proactive
  dependency (^0.4.31) and the ./proactive subpath export.

Why opt-in
- Workforce's runtime doesn't currently use agent-assistant/sessions for
  stateful turn tracking — ctx is event-driven and stateless per
  invocation. Handlers that compose with agent-assistant tooling import
  these helpers; otherwise the runtime stays unchanged. When workforce
  adopts session-scoped memory/scheduling, the wiring lifts up into
  buildCtx so the bridge becomes implicit.

Notes
- Includes a one-line fix to examples/openclaw-routing.ts pulling
  selection.runtime.* → selection.* per #95's PersonaSelection flatten.
  Same fix applied by sub-agents on #92 / #94 rebases.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
khaliqgant added a commit that referenced this pull request May 13, 2026
)

* feat(runtime): proactive-runtime interop bridge to @agent-assistant

Adds a thin, opt-in bridge between workforce's WorkforceCtx and the
@agent-assistant/proactive runtime-interop primitives that just shipped
in agent-assistant#91.

What ships
- packages/runtime/src/proactive.ts — two helpers:
  - toProactiveSession(ctx, { agentId? }): maps a workforce ctx into the
    RuntimeInteropSession descriptor agent-assistant session/memory/
    scheduling primitives consume. Defaults agentId to ctx.agentName.
  - schedulerBindingFromCtx(ctx): produces a ContextSchedulerBinding
    that routes proactive wake-up requests through ctx.schedule.at /
    ctx.schedule.cancel. Lets agent-assistant's proactive engine
    schedule follow-ups using workforce's own schedule context.
  - Re-exports ContextSchedulerBinding, RuntimeInteropSession,
    RuntimeScheduleContext for callers building custom adapters.
- packages/runtime/src/proactive.test.ts — 4 tests covering session
  shape, agentId override, schedule.at routing, schedule.cancel routing.
- packages/runtime/package.json — adds @agent-assistant/proactive
  dependency (^0.4.31) and the ./proactive subpath export.

Why opt-in
- Workforce's runtime doesn't currently use agent-assistant/sessions for
  stateful turn tracking — ctx is event-driven and stateless per
  invocation. Handlers that compose with agent-assistant tooling import
  these helpers; otherwise the runtime stays unchanged. When workforce
  adopts session-scoped memory/scheduling, the wiring lifts up into
  buildCtx so the bridge becomes implicit.

Notes
- Includes a one-line fix to examples/openclaw-routing.ts pulling
  selection.runtime.* → selection.* per #95's PersonaSelection flatten.
  Same fix applied by sub-agents on #92 / #94 rebases.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore(runtime): bump proactive runtime

* fix(runtime): clarify proactive scheduler binding ctx-scope and slot id

Address PR #96 review feedback:

- Doc on schedulerBindingFromCtx incorrectly claimed the binding did
  not capture ctx and was safe to reuse across event invocations. The
  adapter closures do close over ctx, so reuse would silently route
  through stale handles. Doc updated to mark the binding request-scoped.

- The synthetic per-timestamp bindingId could not map back to anything
  registered with ctx.schedule because ScheduleContext.at does not
  accept a caller-supplied name. Return a stable per-agent slot name
  (proactive-<agentName>) instead, so a pre-registered persona schedule
  slot can be cancelled by cancelWakeUp. Cancellation caveat documented.

- Tests updated for the new stable slot id.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Ricky Schema Cascade <ricky@agent-relay.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant