Skip to content

feat(persona-registry): load personal agents from the workforce home - #322

Open
willwashburn wants to merge 7 commits into
mainfrom
feat/user-agents-layer
Open

feat(persona-registry): load personal agents from the workforce home#322
willwashburn wants to merge 7 commits into
mainfrom
feat/user-agents-layer

Conversation

@willwashburn

@willwashburn willwashburn commented Aug 20, 2026

Copy link
Copy Markdown
Member

~/.agentworkforce/workforce/agents/<name>/persona.json is a cascade layer — the personal mirror of the cwd:agents layer added in #316. An agent that ships its own handler is available in every repo instead of only the one it was checked into.

$ agentworkforce sources list
CASCADE  CONFIG  SOURCE       EXISTS  DIR
1        -       cwd          yes     <repo>/.agentworkforce/workforce/personas
2        -       cwd:agents   yes     <repo>/.agentworkforce/workforce/agents
3        1       personal     no      ~/.agentworkforce/workforce/personas
4        -       user:agents  no      ~/.agentworkforce/workforce/agents
5        -       built-in     yes     (built-in)

Rank

It rides directly behind the personal personas dir rather than sitting at a fixed depth. The two are a pair — loose overrides and agents-with-handlers at the same scope — so moving the personas dir in the cascade moves both, and a team source added with sources add --position 1 still outranks both. A repo agent outranks a personal one of the same id, and merges over it, so a project can pin a model or channel on an agent you carry everywhere.

Directory derivation

Derived from wherever the personal personas dir resolves (dirname(userPersonaDir)/agents), not recomputed from AGENT_WORKFORCE_HOME. Recomputing would make embedders and tests that pass an explicit personas dir silently read the developer's real ~/.agentworkforce/workforce/agents — the registry is used by fleet nodes and test harnesses that isolate lookup on purpose.

user:agents displays as personal:agents, following the existing userpersonal rename; the internal string is unchanged for --json consumers.

Verification

Exercised against the real ../sales checkout for the cascade listing. Six new tests cover discovery, relative-skill resolution against the agent's own directory, repo-over-personal precedence with merge, the display label, and a missing directory. local-personas suite passes (56).

Semver: minor — new cascade layer and new userAgentDirFor export.

🤖 Generated with Claude Code

Review in cubic

@coderabbitai

coderabbitai Bot commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: d0527bf7-f397-4ab9-9987-74dd7fff45a4

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

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

Copy link
Copy Markdown

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: 3b90217ede

ℹ️ 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 on lines +405 to +406
return dir === config.userPersonaDir
? [entry, userAgentSourceDir(config.userPersonaDir)]

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 Preserve handler fields when loading personal agents

For a handler-style personal agent produced by agentworkforce persona compile, persona.json contains intent and onEvent and may legitimately omit harness, model, and systemPrompt. Routing it through this new registry layer does not work: parseOverride discards onEvent, then standaloneSpecFromOverride treats the file as an interactive standalone persona and rejects the omitted runtime fields, so the advertised agent-with-handler is omitted from byId with a warning. Load these nested agent files as full handler-capable persona specs, or preserve onEvent and make standalone validation handler-aware.

Useful? React with 👍 / 👎.

@cubic-dev-ai cubic-dev-ai Bot left a comment

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.

All reported issues were addressed across 4 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread packages/persona-registry/src/local-personas.ts
Comment thread packages/cli/README.md
An agent driven by its `onEvent` entry has no interactive launch to configure,
so `harness`, `model`, and `systemPrompt` — already optional on `PersonaSpec` —
are optional here too. Requiring them kept exactly the agents the `agents/`
directory was added for out of the registry, reporting a valid deployable
persona as malformed.

`onEvent` and `cloud` now survive parse and merge. An overlay that tweaks env no
longer strips the handler entry that makes its base deployable, so the merged
spec is a complete agent rather than a partial one.

`harnessSettings` stays required: `PersonaSpec` types it non-optional, and
`reasoning`/`timeoutSeconds` have no defensible default to invent on a
persona's behalf.

`onEvent` is validated as a relative path that cannot escape the agent
directory, matching the sidecar rule.

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

Copy link
Copy Markdown
Member Author

Both P1s are the same defect, and correct — a handler-style agent whose compiled persona.json omits harness/model/systemPrompt is dropped, because parseOverride discarded onEvent and the standalone builder then judged it as an interactive persona.

It is not specific to this layer: cwd:agents (#316, already on main) has it too. I reproduced it there first and fixed it at the source in #323, which teaches the registry about handler agents — onEvent/cloud survive parse and merge, and the interactive fields become optional when onEvent is present.

# with #323
$ agentworkforce list
digest    cwd:agents    —    —    documentation

So this PR needs #323 to land, not a change of its own. One caveat #323 documents rather than solves: harnessSettings stays required, because PersonaSpec types it non-optional and reasoning/timeoutSeconds have no default worth inventing on a persona's behalf.

The P3 on the README is fixed in 9f3ac0c — the cascade list now names personal:agents as the table label and user:agents as the --json value. One correction to the comment: list --json and sources list --json emit the raw user:agents; only the rendered tables map through formatPersonaSourceLabel.

@willwashburn

Copy link
Copy Markdown
Member Author

Correction: the README fix is f08bbbb, not the hash I quoted above.

Hand-rolling the guard drifted from persona-kit twice over. It validated the
raw string and stored a trimmed copy, so `" ../x/agent.ts "` cleared the `..`
check as the segment `" .."` and escaped the agent directory once trimmed. And
it never checked the handler extension, so `onEvent: "README.md"` counted as a
handler and skipped the interactive fields the persona never declared.

`parseOnEvent` owns both rules and returns the exact string it validated, so
the stored value cannot differ from the one that passed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@willwashburn
willwashburn force-pushed the feat/user-agents-layer branch from f08bbbb to 662b2da Compare August 20, 2026 17:18
@willwashburn
willwashburn changed the base branch from main to feat/warn-stale-agent-persona August 20, 2026 17:18
@willwashburn

Copy link
Copy Markdown
Member Author

Rebased onto #321 and retargeted there — same reason as #321, and this PR needed #323 anyway for its two P1s.

Merge order: #318#323#321#322.

Full stack verified together: 65 tests in local-personas, 322 across the CLI suite.

willwashburn and others added 5 commits August 20, 2026 13:24
Order matters in both directions. Validating the raw string and storing a
trimmed copy let `" ../x/agent.ts "` clear the `..` check as the segment `" .."`
and escape once trimmed. Validating without trimming stored `" ./agent.ts"`,
which passes every check and then resolves against a directory named `" ."` at
deploy.

Trimming before `parseOnEvent` makes the validated value and the stored value
the same string.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
An agent directory whose `persona.json` is older than its `persona.ts` loads
the compiled spec with none of the edits sitting in the authoring file, and
nothing about the result looks wrong. #316 warned about a persona that was
never compiled; this covers the quieter case where it was compiled once.

The persona is still served. Dropping it would turn a forgotten compile into a
missing persona, which is a worse failure than an out-of-date one.

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

A directory that still carries an abandoned `persona.ts` after development
moved to `persona.js` was measured against the file nobody edits, so a
`persona.json` newer than the dead source but older than the live one read as
fresh.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`~/.agentworkforce/workforce/agents/<name>/persona.json` is a cascade layer,
the personal mirror of the `cwd:agents` layer added in #316. An agent that
ships its own handler is available in every repo instead of only the one it
was checked into.

It rides directly behind the personal personas dir rather than at a fixed
depth, so moving that dir in the cascade moves the pair together. A repo agent
outranks a personal one of the same id.

The directory is derived from wherever the personal personas dir resolves, so
AGENT_WORKFORCE_CONFIG_DIR and embedder overrides carry it along instead of
silently reading the developer's real home.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The cascade list documented only the internal source string, so a reader who
then ran `sources list` saw a label the docs never mention.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@willwashburn
willwashburn force-pushed the feat/warn-stale-agent-persona branch from 7303cc5 to 3425fa1 Compare August 20, 2026 17:26
@willwashburn
willwashburn force-pushed the feat/user-agents-layer branch from 662b2da to 91c03cd Compare August 20, 2026 17:26
@willwashburn
willwashburn changed the base branch from feat/warn-stale-agent-persona to main August 20, 2026 17:30
@willwashburn willwashburn reopened this Aug 20, 2026
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