You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Replaces the framing in #824. This consumes the new @agentworkforce/persona-kit package being published in AgentWorkforce/workforce#71 (tracking sequence: workforce issues #64–#71).
Written as if persona-kit already exists at @agentworkforce/persona-kit@^1.0.0 — depends on the workforce-side publish landing first.
Issue #824 proposed adding skills install behind an opt-in installSkills: true flag. That's a band-aid. A persona's DNA is the full bundle — skills + MCP servers + mount policy + sidecar markdown + inputs + system prompt. AgentRelay.spawnPersona today silently drops most of that bundle. The header comment of packages/sdk/src/personas.ts literally says:
Skills installation, mount policy, sidecar markdown, input rendering, and routing profiles are deliberately not handled here
A persona stripped of skills and mounts isn't a persona — it's a system prompt. That's not what relay should ship. Skills install (and mount, sidecar, input render) is mandatory, not opt-in. The legitimate "I want to look without running" use case is satisfied by a separate dry-run API.
Goal
Drop @agentworkforce/harness-kit and @agentworkforce/workload-router deps; replace with @agentworkforce/persona-kit.
Gut relay/packages/sdk/src/personas.ts — most of it duplicates work persona-kit now owns.
Rewrite AgentRelay.spawnPersona (in relay/packages/sdk/src/relay.ts:771–829) to honor the full persona schema via persona-kit's buildPersonaSpawnPlan + executePersonaSpawnPlan.
Add a new public method AgentRelay.getPersonaSpawnPlan(personaId, options?) returning the dry-run plan for authoring tools (persona-maker, validators).
Update both the MDX and MD docs (per .claude/rules/docs-sync.md).
File-by-file work
relay/packages/sdk/package.json
Remove @agentworkforce/harness-kit.
Remove @agentworkforce/workload-router (relay's SDK doesn't use routing profiles or persona catalog).
Add @agentworkforce/persona-kit@^1.0.0.
relay/packages/sdk/src/personas.ts
This file currently spans ~550 lines because it duplicates parsing + tier resolution + spec building that persona-kit now owns. Gut it. After this PR it should be ~80 lines:
import{loadPersonas,resolvePersonaTier,buildPersonaSpawnPlan,executePersonaSpawnPlan,typePersonaSpec,typeResolvedPersona,typePersonaSpawnPlan,typeHarness,typePersonaTier,typePersonaSkill,typePersonaMount,typePersonaInputSpec,typeSkillMaterializationPlan,HARNESS_VALUES,PERSONA_TIERS,}from'@agentworkforce/persona-kit';// Re-export the canonical types for SDK consumersexport{loadPersonas,resolvePersonaTier,buildPersonaSpawnPlan,HARNESS_VALUES,PERSONA_TIERS,};exporttype{PersonaSpec,ResolvedPersona,PersonaSpawnPlan,Harness,PersonaTier,PersonaSkill,PersonaMount,PersonaInputSpec,SkillMaterializationPlan,};exportinterfacePersonaLoadOptions{cwd?: string;searchDirs?: string[];extraDirs?: string[];tier?: PersonaTier;}/** Default search-dir cascade for relay (cwd → user home → AgentWorkforce home). */functiondefaultRelaySearchDirs(cwd?: string): string[]{// Match the existing relay cascade: agentworkforce/personas, .agentworkforce/workforce/personas,// ~/.agentworkforce/workforce/personas, $AGENT_WORKFORCE_HOME/personas// [...]}exportfunctionloadPersona(id: string,options: PersonaLoadOptions={}): ResolvedPersona{constsearchDirs=options.searchDirs??defaultRelaySearchDirs(options.cwd);constdirs=[...searchDirs, ...(options.extraDirs??[])];constloaded=loadPersonas({cwd: options.cwd,searchDirs: dirs});constspec=loaded.byId.get(id);if(!spec)thrownewError(`Persona '${id}' not found in: ${loaded.paths.join(', ')}`);returnresolvePersonaTier(spec,options.tier??'best');}exportinterfacePersonaSpawnOptions{cwd?: string;installRoot?: string;envOverrides?: Record<string,string>;}exportfunctiongetPersonaSpawnPlan(personaId: string,options: PersonaLoadOptions&PersonaSpawnOptions={},): PersonaSpawnPlan{constpersona=loadPersona(personaId,options);returnbuildPersonaSpawnPlan(persona,{cwd: options.cwd,installRoot: options.installRoot,envOverrides: options.envOverrides,});}
The relay-specific concerns that stay: search-dir cascade defaults, PersonaLoadOptions shape relay consumers expect.
relay/packages/sdk/src/relay.ts (lines 771–829)
Rewrite AgentRelay.spawnPersona:
asyncspawnPersona(personaId: string,options: SpawnPersonaOptions): Promise<Agent>{constspawnCwd=options.cwd??process.cwd();constpersona=loadPersona(personaId,{cwd: spawnCwd,tier: options.tier});constplan=buildPersonaSpawnPlan(persona,{cwd: spawnCwd,installRoot: options.skillsInstallRoot,envOverrides: options.env,});// Always materialize the full persona — skills, MCPs, mount, sidecars, inputs.// No opt-in flag. A persona without these is just a system prompt, which isn't// what relay ships.consthandle=awaitexecutePersonaSpawnPlan(plan,{cwd: spawnCwd});try{consttask=composePersonaTask(plan,options.task);constagent=awaitthis.spawnPty({name: options.name??persona.id,cli: plan.cli,args: plan.args,env: plan.env,
task,
...options,});agent.waitForExit().finally(()=>handle.dispose());returnagent;}catch(err){awaithandle.dispose();throwerr;}}
Add a new public method:
/** * Build a `PersonaSpawnPlan` for the persona without executing it. Useful for * authoring tools that want to validate a persona's skill sources, mount * policy, and harness argv before committing the JSON. * * Calls under the hood: `loadPersona` → `buildPersonaSpawnPlan`. No filesystem * writes, no subprocesses. */getPersonaSpawnPlan(personaId: string,options?: PersonaLoadOptions&PersonaSpawnOptions,): PersonaSpawnPlan{returngetPersonaSpawnPlan(personaId,options);}
relay/packages/sdk/src/personas.test.ts (or wherever persona tests live)
Replace tests for the gutted code with tests against the persona-kit-backed surface:
loadPersona(id) — happy path against a fixture; throws with cascade paths in the error when missing.
getPersonaSpawnPlan(id) — returns a plan matching what workforce CLI builds for the same persona on the same harness. Assert by snapshot.
spawnPersona(id) end-to-end with a fake skill source (test-only resolver mapped to echo) — verify execution order, verify cleanup runs after agent.waitForExit().
Failure-path: persona declares a hallucinated skill source → spawnPersona throws before spawnPty is called; no orphan process; handle.dispose() reverses partial state.
Verify relay/packages/sdk/dist/workers.js (the workerd entry) doesn't pull node: modules transitively from persona-kit. If it does, switch to @agentworkforce/persona-kit/plan (pure entry) for the workers build and @agentworkforce/persona-kit (full) only for the node entry.
web/content/docs/reference-sdk.mdx — document AgentRelay.spawnPersona's new contract (always installs skills/mount/sidecars/inputs), document new getPersonaSpawnPlan method, link to persona-kit's README for the canonical persona schema reference. Remove/update the old caveat about skills being deferred.
docs/reference-sdk.md — same content, MDX components stripped per the sync rule.
If web/content/docs/personas.mdx (or similar) exists — update there too.
Update packages/sdk/README.md if it lists persona behavior; refresh.
Migration notes for SDK consumers
This is a breaking change for anyone calling spawnPersona against a persona that declares skills/mount/sidecars and was relying on those being silently dropped. Before this change, relay's spawn was always cheap (no installs, no fs writes). After this change, spawn does everything the workforce CLI does.
For the rare consumer who wants the old "just spawn the harness, ignore the rest of the schema" behavior:
Option A: Use AgentRelay.spawnPty directly with the cli/args from getPersonaSpawnPlan(id). They get the harness invocation without the side effects.
Option B: Author personas without skills/mount/sidecars. The DNA is opt-in at authoring time, not opt-in at spawn time.
Document both in reference-sdk.mdx.
Constraints
Persona-kit's API is the contract; relay should not re-implement parsing or tier-resolution. If something feels missing in persona-kit, file an issue against AgentWorkforce/workforce instead of working around it in relay.
Don't add an opt-in flag for skills install. The right opt-in is getPersonaSpawnPlan (don't run anything; just look). Adding a installSkills: false option re-creates the original problem.
No back-compat shim for the dropped harness-kit / workload-router imports. Per the workforce-side migration, those are gone.
Verification
pnpm install resolves cleanly with the new dep set; @agentworkforce/harness-kit and @agentworkforce/workload-router are absent from the lockfile.
pnpm test in relay/packages/sdk passes.
End-to-end test (manual or CI):
Pick a persona that exercises the full schema: skills (vercel-labs/skills#find-skills or similar), mount policy, claudeMdContent sidecar, inputs with env-substitution.
Run agentworkforce <persona>. Record everything: skill installs, sidecar files, mount apply, harness argv, env at spawn.
Run AgentRelay.spawnPersona(<persona>) against the same persona. Assert byte-equality with the workforce-side recording.
Run the agent for ~10 seconds; verify .claude/skills/find-skills/SKILL.md (or harness equivalent) exists and is non-empty during the session.
Failure test: persona with a hallucinated skill source → spawnPersona throws before spawnPty; no orphan process; no half-installed .claude/skills/<name> left behind.
AgentRelay.getPersonaSpawnPlan(<persona>) returns a plan whose skills.installs[*].argv matches step 2's recorded npx commands byte-for-byte.
Docs render correctly: visit the local docs build (pnpm --filter web dev or equivalent), confirm reference-sdk page reflects the new API.
The dist/personas.d.ts exports the right types from persona-kit.
Out of scope
Routing profiles / personaCatalog. Workforce-CLI internals; relay doesn't need them. The dropped @agentworkforce/workload-router dep reflects that.
Persona authoring tool UX. getPersonaSpawnPlan gives them what they need; how they render a plan is their problem.
Cross-repo schema drift CI. Once persona-kit is the single source of truth (which it is after this PR), this is unnecessary.
Replaces the framing in #824. This consumes the new
@agentworkforce/persona-kitpackage being published in AgentWorkforce/workforce#71 (tracking sequence: workforce issues #64–#71).Written as if persona-kit already exists at
@agentworkforce/persona-kit@^1.0.0— depends on the workforce-side publish landing first.Why the original #824 framing was wrong
Issue #824 proposed adding skills install behind an opt-in
installSkills: trueflag. That's a band-aid. A persona's DNA is the full bundle — skills + MCP servers + mount policy + sidecar markdown + inputs + system prompt.AgentRelay.spawnPersonatoday silently drops most of that bundle. The header comment ofpackages/sdk/src/personas.tsliterally says:A persona stripped of skills and mounts isn't a persona — it's a system prompt. That's not what relay should ship. Skills install (and mount, sidecar, input render) is mandatory, not opt-in. The legitimate "I want to look without running" use case is satisfied by a separate dry-run API.
Goal
@agentworkforce/harness-kitand@agentworkforce/workload-routerdeps; replace with@agentworkforce/persona-kit.relay/packages/sdk/src/personas.ts— most of it duplicates work persona-kit now owns.AgentRelay.spawnPersona(inrelay/packages/sdk/src/relay.ts:771–829) to honor the full persona schema via persona-kit'sbuildPersonaSpawnPlan+executePersonaSpawnPlan.AgentRelay.getPersonaSpawnPlan(personaId, options?)returning the dry-run plan for authoring tools (persona-maker, validators)..claude/rules/docs-sync.md).File-by-file work
relay/packages/sdk/package.json@agentworkforce/harness-kit.@agentworkforce/workload-router(relay's SDK doesn't use routing profiles or persona catalog).@agentworkforce/persona-kit@^1.0.0.relay/packages/sdk/src/personas.tsThis file currently spans ~550 lines because it duplicates parsing + tier resolution + spec building that persona-kit now owns. Gut it. After this PR it should be ~80 lines:
The relay-specific concerns that stay: search-dir cascade defaults,
PersonaLoadOptionsshape relay consumers expect.relay/packages/sdk/src/relay.ts(lines 771–829)Rewrite
AgentRelay.spawnPersona:Add a new public method:
relay/packages/sdk/src/personas.test.ts(or wherever persona tests live)Replace tests for the gutted code with tests against the persona-kit-backed surface:
loadPersona(id)— happy path against a fixture; throws with cascade paths in the error when missing.getPersonaSpawnPlan(id)— returns a plan matching what workforce CLI builds for the same persona on the same harness. Assert by snapshot.spawnPersona(id)end-to-end with a fake skill source (test-only resolver mapped toecho) — verify execution order, verify cleanup runs afteragent.waitForExit().spawnPersonathrows beforespawnPtyis called; no orphan process;handle.dispose()reverses partial state.JSON.parse(JSON.stringify(plan))round-trips byte-for-byte.Bundle check
relay/packages/sdk/dist/workers.js(the workerd entry) doesn't pullnode:modules transitively from persona-kit. If it does, switch to@agentworkforce/persona-kit/plan(pure entry) for the workers build and@agentworkforce/persona-kit(full) only for the node entry.Docs sync
Per .claude/rules/docs-sync.md, every MDX change mirrors to MD.
web/content/docs/reference-sdk.mdx— documentAgentRelay.spawnPersona's new contract (always installs skills/mount/sidecars/inputs), document newgetPersonaSpawnPlanmethod, link to persona-kit's README for the canonical persona schema reference. Remove/update the old caveat about skills being deferred.docs/reference-sdk.md— same content, MDX components stripped per the sync rule.web/content/docs/personas.mdx(or similar) exists — update there too.packages/sdk/README.mdif it lists persona behavior; refresh.Migration notes for SDK consumers
This is a breaking change for anyone calling
spawnPersonaagainst a persona that declares skills/mount/sidecars and was relying on those being silently dropped. Before this change, relay's spawn was always cheap (no installs, no fs writes). After this change, spawn does everything the workforce CLI does.For the rare consumer who wants the old "just spawn the harness, ignore the rest of the schema" behavior:
AgentRelay.spawnPtydirectly with the cli/args fromgetPersonaSpawnPlan(id). They get the harness invocation without the side effects.Document both in
reference-sdk.mdx.Constraints
getPersonaSpawnPlan(don't run anything; just look). Adding ainstallSkills: falseoption re-creates the original problem.Verification
pnpm installresolves cleanly with the new dep set;@agentworkforce/harness-kitand@agentworkforce/workload-routerare absent from the lockfile.pnpm testinrelay/packages/sdkpasses.vercel-labs/skills#find-skillsor similar), mount policy,claudeMdContentsidecar, inputs with env-substitution.agentworkforce <persona>. Record everything: skill installs, sidecar files, mount apply, harness argv, env at spawn.AgentRelay.spawnPersona(<persona>)against the same persona. Assert byte-equality with the workforce-side recording..claude/skills/find-skills/SKILL.md(or harness equivalent) exists and is non-empty during the session.spawnPersonathrows beforespawnPty; no orphan process; no half-installed.claude/skills/<name>left behind.AgentRelay.getPersonaSpawnPlan(<persona>)returns a plan whoseskills.installs[*].argvmatches step 2's recorded npx commands byte-for-byte.pnpm --filter web devor equivalent), confirmreference-sdkpage reflects the new API.dist/personas.d.tsexports the right types from persona-kit.Out of scope
personaCatalog. Workforce-CLI internals; relay doesn't need them. The dropped@agentworkforce/workload-routerdep reflects that.getPersonaSpawnPlangives them what they need; how they render a plan is their problem.Closes
Closes #824.
Depends on
This issue can start drafting in parallel with the workforce side, but cannot land until #71 publishes
@agentworkforce/persona-kit@1.0.0.