Shared AgentWorkforce primitives for persona-driven orchestration.
A persona is the runtime source of truth:
- prompt (
systemPrompt) - model
- harness
- harness settings
- optional
skillsarray of{ id, source, description }entries for reusable capability guidance (e.g. prpm.dev packages)
Each persona supports service tiers:
bestbest-valueminimum
Tiering controls depth, latency budget, and model cost envelope — not the quality bar. All tiers should enforce the same correctness/safety standards; lower tiers should be more concise, not lower-quality.
A routing profile is policy-only. It does not carry runtime fields; it only selects which persona tier to use per intent and explains why.
The agent-workforce binary (published as @agentworkforce/cli) is the
fastest way to actually run a persona. It resolves the persona from the
built-in catalog or your local overrides, installs any declared skills,
and execs the harness CLI (claude, codex, or opencode) with the right
model, system prompt, env vars, MCP servers, and permissions wired up.
From the monorepo checkout:
corepack pnpm -r build
corepack pnpm --filter @agentworkforce/cli link --globalagent-workforce is now on your PATH. (Or run the built bin directly:
./packages/cli/dist/cli.js ….)
agent-workforce agent <persona>[@<tier>] [task...]
agent-workforce list [flags]
agent-workforce harness check
agent— run a persona.- No task → drops you into an interactive harness session.
- Task string → runs one-shot via
usePersona().sendMessage()and streams output. <tier>isbest|best-value|minimum(default:best-value).<persona>resolves across three layers, highest first:./.agent-workforce/*.json— project-local~/.agent-workforce/*.json— user-local- Built-in personas in
/personas/
list— print the catalog of personas from the cascade (pwd → home → library). Columns: persona, source, harness, model, rating, description. By default shows one row per persona at the recommended tier for its intent. Flags:--all,--json,--filter-rating <tier>,--filter-harness <harness>,--no-display-description. See packages/cli/README.md for details.harness check— probe which harnesses (claude,codex,opencode) are installed and runnable on this machine. Prints a table with status, version, and the resolved path for each.
Each local layer is a partial overlay — only the fields you set replace the value from the next lower layer; everything else cascades through.
# One-shot against the built-in code reviewer
agent-workforce agent review@best-value "look at the diff on this branch"
# Interactive PostHog session — the built-in persona ships with the PostHog
# MCP server wired up and its tools auto-approved.
export POSTHOG_API_KEY=phx_…
agent-workforce agent posthog@bestProject-local ./.agent-workforce/my-posthog.json:
{
"id": "my-posthog",
"extends": "posthog",
"env": { "POSTHOG_API_KEY": "$POSTHOG_API_KEY" },
"permissions": {
"allow": ["mcp__posthog__insights-list", "mcp__posthog__events-query"]
}
}agent-workforce agent my-posthog@best inherits everything from the built-in
posthog persona, layers your env var and narrower allow list on top, and
launches claude against the PostHog MCP server with only the two named tools
auto-approved.
The full docs — cascade rules, ${VAR} interpolation, MCP transport
options, permission grammar, troubleshooting — live in
packages/cli/README.md.
Interactive claude sessions stage skills outside the repo by default.
The CLI materializes a Claude Code plugin under the user's home directory
and passes it via --plugin-dir, so the session sees exactly the persona's
declared skills — and nothing the repo happens to have in .claude/skills/.
~/.agent-workforce/
└── sessions/<personaId>-<timestamp>-<rand>/
└── claude/
└── plugin/ ← passed as --plugin-dir
├── .claude-plugin/plugin.json ← generated scaffold
├── skills → .claude/skills ← relative symlink
└── .claude/skills/<name>/SKILL.md ← prpm install output
On exit the whole stage dir is removed with a single rm -rf. To fall back
to the legacy behavior and install into the repo's .claude/skills/, pass
--install-in-repo:
agent-workforce agent --install-in-repo code-reviewer@bestV1 scope: claude interactive only. codex, opencode, and one-shot runs still
use the repo-relative install path. A content-addressed
~/.agent-workforce/cache/ layer for reusing installs across sessions is
planned but not yet wired up. See
packages/cli/README.md#skill-staging
for the full mechanics.
For interactive claude sessions, --clean launches the harness inside a
@relayfile/local-mount
sandbox that hides repo-level Claude Code configuration from the model:
Hidden in --clean mode |
Still visible |
|---|---|
CLAUDE.md (at any depth) |
~/.claude/CLAUDE.md (user-level) |
CLAUDE.local.md |
~/.claude/skills/ (user-level skills) |
.claude/ |
persona's staged skills (via --plugin-dir) |
.mcp.json |
persona's own mcpServers block |
| your keychain auth (unchanged) |
agent-workforce agent --clean posthog@bestThe repo tree is mirrored into
~/.agent-workforce/sessions/<id>/mount/ via symlinks; claude sees the
mount as its cwd. Writes inside the mount sync back to the real repo on
exit. Ignore semantics follow gitignore — .claude hides nested variants
like packages/foo/.claude/ too.
Scope: interactive claude only. Pass it to codex/opencode or to a
one-shot run and the flag is ignored with a note. --clean and
--install-in-repo are mutually exclusive — they ask for opposite things.
Caveat: user-level Claude Code config in ~/.claude/ still loads
inside the session. --clean hides the repo's context, not the user's.
If you need to hide user-level config too, launch under a scratch
$HOME. See packages/cli/README.md#clean-mode
for the full mount layout and semantics.
packages/workload-router— TypeScript SDK for typed persona + routing profile resolution (harness-agnostic).packages/harness-kit— Composable primitives for launching a persona's harness: env-ref resolution, MCP server translation, per-harness argv building. The layer the CLI sits on top of. Depend on this directly if you're building your own orchestrator on top of@agentworkforce/workload-routerand want the same behaviors.packages/cli—agent-workforcecommand-line front end: spawn a persona's harness (claude/codex/opencode) from the shell, interactively or one-shot. See packages/cli/README.md for the full docs, and the CLI section below for a quick tour.
personas/frontend-implementer.jsonpersonas/code-reviewer.jsonpersonas/architecture-planner.jsonpersonas/requirements-analyst.jsonpersonas/debugger.jsonpersonas/security-reviewer.jsonpersonas/technical-writer.jsonpersonas/verifier.jsonpersonas/test-strategist.jsonpersonas/tdd-guard.jsonpersonas/flake-hunter.jsonpersonas/opencode-workflow-specialist.jsonpersonas/npm-provenance-publisher.jsonpersonas/posthog.jsonpersonas/persona-maker.jsonpersonas/anti-slop-auditor.json
packages/workload-router/routing-profiles/default.jsonpackages/workload-router/routing-profiles/schema.json
The recommended entry point is usePersona(intent) — a synchronous,
side-effect-free factory that resolves a persona and returns grouped install
metadata plus a sendMessage() closure. Calling it does nothing but
pre-compute the routing; nothing is installed or spawned until you call
sendMessage() or run the install command yourself.
import { usePersona } from '@agentworkforce/workload-router';
const { sendMessage } = usePersona('npm-provenance');
// Installs the persona's skills, then runs the persona's harness agent
// with your task. Returns a PersonaExecution — awaitable, with
// `cancel()` and a `runId` promise attached.
//
// `await sendMessage(...)` only resolves on `status: 'completed'`. Non-zero
// exits / timeouts throw PersonaExecutionError; cancellation throws
// AbortError. Both carry the typed ExecuteResult on `err.result`.
try {
const result = await sendMessage('Set up npm trusted publishing for this repo', {
workingDirectory: '.',
timeoutSeconds: 600,
});
// result.status === 'completed' here
} catch (err) {
const execErr = err as Error & {
result?: { status: string; stderr: string; exitCode: number | null };
};
console.error(
'persona run failed',
execErr.result?.status,
execErr.result?.stderr,
);
}Despite the
use*prefix,usePersonais not a React hook. It is a plain synchronous factory with no implicit state — safe to call anywhere.
The full return shape is:
const {
selection,
install,
sendMessage,
} = usePersona('npm-provenance');selection: resolved persona choice and runtime metadata.install: grouped install metadata.install.plan: pure skill-install plan with no side effects.install.command: full install command as an argv array.install.commandString: full install command as a shell string.sendMessage(task, options?): runs the persona and returns an awaitablePersonaExecution.
For the full API — the install-only mode, pre-staged install with
installSkills: false, cancellation via AbortSignal, streaming progress,
the runId timing contract, and the double-install caveat when mixing
modes — see packages/workload-router/README.md.
Low-level primitives (advanced use — prefer usePersona for new code)
If you need to resolve a persona and materialize its skill install plan
without running an agent — or you want to drive install yourself with
custom process management — usePersona is built on top of two pure
helpers you can call directly:
import { resolvePersona, materializeSkillsFor } from '@agentworkforce/workload-router';
import { spawnSync } from 'node:child_process';
const selection = resolvePersona('npm-provenance');
// selection -> { personaId, tier, runtime, skills, rationale }
const plan = materializeSkillsFor(selection);
for (const install of plan.installs) {
// install.installCommand is an argv array (safer for execFile/spawn).
// For a shell string, use `usePersona(...).install.commandString`.
spawnSync(install.installCommand[0], install.installCommand.slice(1), { stdio: 'inherit' });
}These primitives are exported for callers who need direct access to the
plan object or want to skip the sendMessage() workflow entirely. New code
should prefer usePersona — it consolidates routing, install planning,
and agent execution into one call and gives you cancellation / progress /
run-id observability for free.
- Map user request to
intent:implement-frontendreviewarchitecture-planrequirements-analysisdebuggingsecurity-reviewdocumentationverificationtest-strategytdd-enforcementflake-investigationopencode-workflow-correctnessnpm-provenanceposthog
- Call
usePersona(intent, { profile? })to resolve the persona and receive the selected persona, grouped install metadata, and asendMessage()closure bound to its runtime (harness, model, settings, prompt). - Call
sendMessage(task, opts)to install the persona's skills and invoke its harness agent in one step. UseAbortSignal/execution.cancel()for cancellation andonProgressto stream stdout/stderr.
If you need to bypass sendMessage() and spawn the agent yourself — for
example, to integrate with an existing orchestrator — resolvePersona +
materializeSkillsFor remain available as the underlying primitives
(see the collapsed section above).
See runnable mapping example:
examples/openclaw-routing.ts
This keeps runtime configuration in personas, while routing policy stays explicit, typed, and auditable.
A persona can declare a skills array of reusable capability packages (e.g. from prpm.dev):
"skills": [
{
"id": "prpm/npm-trusted-publishing",
"source": "https://prpm.dev/packages/prpm/npm-trusted-publishing",
"description": "OIDC-based npm publish without long-lived tokens"
}
]Persona JSON is harness-agnostic — it declares what skill is needed, not how to install it. The SDK's materializeSkills(skills, harness) / materializeSkillsFor(selection) helper turns the declaration into a concrete install plan, routing each skill to the right on-disk convention per harness:
| Harness | Install flag | Skill directory |
|---|---|---|
claude |
prpm install --as claude |
.claude/skills/ |
codex |
prpm install --as codex |
.agents/skills/ |
opencode |
prpm install --as opencode |
.agents/skills/ |
Each returned SkillInstall carries an argv-style installCommand, installedDir, and installedManifest path. The helper is pure — it never shells out or touches disk — so callers (relay workflows, OpenClaw spawners, ad-hoc scripts) decide how to execute it. Once installed, Claude Code auto-discovers skills from .claude/skills/; for other harnesses, read the manifest off disk and inject it into the agent's task body.
Next step is a benchmark harness to score persona/tier combinations on:
- quality (task pass rate)
- cost
- latency
Then publish a versioned “recommended tier map” so default routing is data-backed.
corepack enable
pnpm install
pnpm run checkThis runs minimal guardrails across the workspace:
lint(currently TypeScript-only)typecheck(package + examples)test(Node test runner)
For iterating on the CLI, harness-kit, workload-router, or persona JSON files, use the watch-mode dev loop instead of rebuilding by hand.
Terminal 1 — start the watchers (leave running):
npm run devFirst runs a cold corepack pnpm -r build so every package's dist/ exists,
then starts tsc --watch on all three packages in parallel. For
workload-router it also runs a persona-JSON watcher: editing any file under
/personas/*.json regenerates
packages/workload-router/src/generated/personas.ts, and tsc picks up the
change and rebuilds dist automatically — full JSON → built artifact flow with
no manual step.
Terminal 2 — invoke the CLI against the latest build:
npm run dev:cli -- harness check
npm run dev:cli -- agent review@best-value "look at the diff on this branch"The -- is required so npm forwards everything after it as argv to the CLI
(otherwise npm consumes flags like --model for itself).
Edit → save → re-run in terminal 2. TypeScript errors show up in terminal 1.
Per-package dev: if you only want to watch one package, run
corepack pnpm --filter @agentworkforce/<name> run dev (where <name> is
cli, harness-kit, or workload-router).