feat(persona-registry): let handler agents into the cascade - #323
feat(persona-registry): let handler agents into the cascade#323willwashburn wants to merge 3 commits into
Conversation
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>
|
Warning Review limit reached
Next review available in: 4 minutes Limit details: You’ve used the included review currently available. You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. How can I continue?Wait for the limit to reset, then comment An organization admin can change what happens after included review limits in Billing. How do review limits work?CodeRabbit enforces per-developer PR review limits within each organization. For paid Pro and Pro+ reviews, CodeRabbit uses a developer's included PR review attempts over the past 7 days to set the current hourly allowance. At typical activity levels, the full plan allowance applies. Higher sustained activity can lower the allowance until earlier attempts leave the 7-day window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (2)
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. Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4122f83ceb
ℹ️ 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".
| if (typeof raw.onEvent !== 'string' || !raw.onEvent.trim()) { | ||
| throw new Error(`${context}.onEvent must be a non-empty string if provided`); | ||
| } | ||
| assertSafeRelativePath(raw.onEvent, `${context}.onEvent`); |
There was a problem hiding this comment.
Validate the normalized handler path
Validate the same trimmed value that is stored. For example, onEvent: " ../outside/agent.ts " passes this check because its first segment is " ..", but line 672 trims it to ../outside/agent.ts; downstream deployment then resolves and bundles a handler outside the agent directory, defeating the containment guard.
Useful? React with 👍 / 👎.
| if (value.split(/[\\/]+/).some((segment) => segment === '..')) { | ||
| throw new Error(`${context} must not contain ".." segments`); | ||
| } | ||
| } |
There was a problem hiding this comment.
Enforce a handler source-file extension
Require the same .ts/.tsx/.mts/.cts/.js/.mjs/.cjs extensions accepted by persona-kit's parseOnEvent. As written, a standalone file with onEvent: "README.md" is classified as a handler, so it can omit harness, model, and systemPrompt and still enter the cascade even though deploy will reject it as an invalid handler.
Useful? React with 👍 / 👎.
| // An overlay that only tweaks env must not strip the handler entry that | ||
| // makes the base a deployable agent. | ||
| const onEvent = override.onEvent ?? base.onEvent; |
There was a problem hiding this comment.
Preserve the handler path's declaring directory
When a personas/digest.json overlay inherits ./agent.ts from agents/digest/persona.json, this retains the string but loses its provenance: loaded.paths points to the winning overlay file, while onEvent is defined relative to the lower file. Any consumer following the PersonaSpec contract and resolving the handler relative to the returned persona path therefore looks for personas/agent.ts rather than agents/digest/agent.ts, so the newly tested env-only overlay no longer identifies a deployable handler.
Useful? React with 👍 / 👎.
|
Verified the failure mode this opens up, since handler personas can now reach code that previously never saw one.
The CLI's own
And the So the interactive surfaces were built expecting handler personas in the registry; the standalone builder was the one place that hadn't caught up. This change completes that design rather than altering it, and the new failure mode is a typed error pointing at I did not run |
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>
|
All three P2s validated. Two are fixed; the third is the open API decision on #320. Trim-vs-validate and the missing extension rule — both correct, both fixed. They shared a cause: I hand-rolled the guard instead of using persona-kit's. It validated the raw string and stored a trimmed copy, so Now delegated to Declaring-directory provenance — correct, and it is the blocked decision. An overlay that inherits Resolving
I would rather land that deliberately than pick a shape here. Until it lands, this PR leaves overlay-shadowed handlers exactly as it found them — the difference is that the inherited |
There was a problem hiding this comment.
All reported issues were addressed across 2 files (changes from recent commits).
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
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>
|
Valid, and it is the correct synthesis of the two findings on this line rather than a reversal of the earlier one. Order matters in both directions:
Trimming before Two tests pin it — the padded-traversal case now asserts the traversal message specifically, and a new case asserts the stored value is normalized. |
Unblocks the P1s found on #320, at their source.
An agent driven by its
onEvententry has no interactive launch to configure.PersonaSpecalready typesharness,model, andsystemPromptas optional and already modelsonEvent/cloud— but the registry's standalone builder demanded all three and dropped the handler fields on the floor. The result: theagents/directory added in #316 could not load the class of agent it was added for, and said so in a way that read as "your persona is malformed".—is honest here: the agent has no harness, and now says so instead of being invisible.Overlays keep the handler
onEventandcloudsurvive parse and merge, so an overlay that only tweaks env no longer strips what makes its base deployable:That is the registry half of #320's second P1. The remaining half is deploy consuming the merged spec instead of a declaring path, which is the open API question on that PR.
What this deliberately does not do
harnessSettingsstays required.PersonaSpectypes it non-optional, andreasoning/timeoutSecondshave no defensible default to invent on a persona's behalf — inventing one would silently run a handler that callsctx.harness.run()with settings nobody chose. Every shipped handler example (examples/weekly-digest,examples/review-agent) declares it. A handler persona that omitsharnessSettingsentirely still cannot load; making that work means deciding whetherPersonaSpec.harnessSettingsbecomes optional, which is a published-type change in@agentworkforce/persona-kitthat the cloud runtime also consumes. Flagging rather than deciding.Integrations, triggers, and schedules are likewise still not modelled by the local override — deploy reads those from the file directly. Merge semantics for them are a separate design question.
Verification
Both original repros re-run against the built CLI. Four new tests cover the handler persona loading without interactive fields, a non-handler standalone still requiring
harness, an overlay preserving the inherited handler entry, andonEventrejected when it escapes the agent directory.local-personassuite passes (55). Lint and typecheck clean.Semver: minor — the registry accepts a persona shape it previously rejected; no existing persona changes behavior.
🤖 Generated with Claude Code