feat(personas-core): add proactive-agent-builder persona + fix optional-input regression#87
feat(personas-core): add proactive-agent-builder persona + fix optional-input regression#87khaliqgant wants to merge 2 commits into
Conversation
parseInputsShape in local-personas.ts was copying description / env /
default off each input spec but silently dropping `optional: true`,
which the resolvePersonaInputs helper relies on to substitute `$NAME`
as an empty string instead of throwing MissingPersonaInputError.
Effect: any pack-distributed persona that followed the canonical
persona-maker pattern (sparse `systemPrompt: "$TASK_DESCRIPTION"` +
`inputs.TASK_DESCRIPTION: { optional: true }`) failed at launch with
"Persona input TASK_DESCRIPTION is required" until the user manually
set the env var, defeating the whole point of the sentinel.
The persona-kit resolver and parser both already support optional;
this just plumbs it through the CLI's local-personas loader. Added a
regression test asserting the flag round-trips on a standalone
local persona JSON.
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Scaffolds a new proactive agent (cron / watch / message-triggered) into
a target project that follows the @agent-relay/agent runtime contract.
Takes a natural-language TASK_DESCRIPTION ("check Reddit daily for
mentions of X, summarize, DM in Slack") and produces a typed agent.ts
under agents/<id>/, wires the bootstrap entry in the Pages Function
router so it runs on Cloudflare today, declares the env vars it
reads, and reports the cron line / webhook URL the user must register.
The operating spec — runtime contract, three trigger templates with
pointers to the canonical proactive-agents reference implementations,
env-injection shim pattern, destination routing for Slack / GitHub /
Linear / Notion, idempotency and dedup conventions — lives in
agentsMdContent. Each tier's systemPrompt is the sparse
"$TASK_DESCRIPTION" sentinel per the persona-maker convention; the
heavy guidance is delivered as AGENTS.md sidecar so it doesn't burn
prompt tokens on every turn.
Adds a new `scaffold-proactive-agent` entry to PERSONA_INTENTS plus a
balanced-default routing rule pinning the intent to `best` tier — a
misshaped agent.ts ships either a dead handler or a duplicate-firing
one, so depth over speed is the right default.
Validated via `agentworkforce agent proactive-agent-builder@<tier>
--dry-run` on both codex and opencode tiers; sidecar resolution,
harness spec build, and skill install all pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
📝 WalkthroughWalkthroughThis PR introduces support for optional persona inputs and registers a new ChangesProactive Agent Builder with Optional Input Support
Estimated code review effort🎯 2 (Simple) | ⏱️ ~12 minutes Possibly related issues
Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
|
Caution Failed to replace (edit) comment. This is likely due to insufficient permissions or the comment being deleted. Error details |
There was a problem hiding this comment.
🧹 Nitpick comments (2)
packages/cli/src/local-personas.test.ts (1)
478-517: ⚡ Quick winAdd a negative regression test for the new invariant.
Please add a companion test that confirms persona loading warns/rejects when an input combines
optional: truewithdefault, so the new parser guard stays protected.Suggested test addition
+test('rejects optional:true combined with default in input spec', () => { + withLayers(({ cwd, homeDir }) => { + writeJson(join(homeDir, 'bad-optional.json'), { + id: 'bad-optional', + intent: 'bad-optional', + tags: ['implementation'], + description: 'Invalid input shape.', + inputs: { + TASK_DESCRIPTION: { + optional: true, + default: 'seed' + } + }, + tiers: { + best: { + harness: 'codex', + model: 'openai-codex/gpt-5.3-codex', + systemPrompt: '$TASK_DESCRIPTION', + harnessSettings: { reasoning: 'high', timeoutSeconds: 30 } + }, + 'best-value': { + harness: 'opencode', + model: 'opencode/gpt-5-nano', + systemPrompt: '$TASK_DESCRIPTION', + harnessSettings: { reasoning: 'medium', timeoutSeconds: 30 } + }, + minimum: { + harness: 'opencode', + model: 'opencode/minimax-m2.5-free', + systemPrompt: '$TASK_DESCRIPTION', + harnessSettings: { reasoning: 'low', timeoutSeconds: 30 } + } + } + }); + const loaded = loadLocalPersonas({ cwd, homeDir }); + assert.equal(loaded.byId.has('bad-optional'), false); + assert.match(loaded.warnings.join('\n'), /cannot combine optional:true with a default/); + }); +});🤖 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.test.ts` around lines 478 - 517, Add a negative regression test alongside the existing "optional input flag is preserved on standalone local personas" test that constructs a persona where an input combines optional: true and default: '...'; call loadLocalPersonas({ cwd, homeDir }) and assert that loaded.warnings contains a warning about the invalid combination (optional + default) and that the parsed persona rejects that invalid input (e.g., spec = loaded.byId.get('standalone-scaffolder') yields either no spec for that id or that spec?.inputs?.TASK_DESCRIPTION.optional !== true / default is not honored). Reference loadLocalPersonas and the TASK_DESCRIPTION input in your test so it fails if the parser guard is removed.packages/personas-core/personas/proactive-agent-builder.json (1)
11-14: ⚡ Quick winClarify
TARGET_DIRpath semantics to avoid conflicting instructions.
Line 12says this must be an absolute path, butLine 13defaults to"."(relative). Please make the description explicitly allow relative paths (resolved from cwd), or change the default to an absolute-path mechanism.Proposed wording tweak
- "description": "Absolute path to the proactive-agents project root (the repo that has `agents/`, `agents/shared/sdk.ts`, and `functions/api/cron/[agent].ts`). Defaults to the current working directory.", + "description": "Path to the proactive-agents project root (the repo that has `agents/`, `agents/shared/sdk.ts`, and `functions/api/cron/[agent].ts`). Relative paths are resolved from the current working directory; default is `.`.",🤖 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/personas/proactive-agent-builder.json` around lines 11 - 14, The description for the TARGET_DIR setting is inconsistent (it says "Absolute path" but the default is "."), so update the personas/proactive-agent-builder.json entry for TARGET_DIR to either: (A) allow relative paths by changing the description to "Absolute or relative path to the proactive-agents project root (resolved from current working directory). Defaults to '.'", or (B) make the default an absolute-path string if you must require absolute paths; reference the TARGET_DIR key when making this change so docs and defaults align. Ensure the wording explicitly states resolution behavior (resolved from cwd) if you choose option A.
🤖 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.
Nitpick comments:
In `@packages/cli/src/local-personas.test.ts`:
- Around line 478-517: Add a negative regression test alongside the existing
"optional input flag is preserved on standalone local personas" test that
constructs a persona where an input combines optional: true and default: '...';
call loadLocalPersonas({ cwd, homeDir }) and assert that loaded.warnings
contains a warning about the invalid combination (optional + default) and that
the parsed persona rejects that invalid input (e.g., spec =
loaded.byId.get('standalone-scaffolder') yields either no spec for that id or
that spec?.inputs?.TASK_DESCRIPTION.optional !== true / default is not honored).
Reference loadLocalPersonas and the TASK_DESCRIPTION input in your test so it
fails if the parser guard is removed.
In `@packages/personas-core/personas/proactive-agent-builder.json`:
- Around line 11-14: The description for the TARGET_DIR setting is inconsistent
(it says "Absolute path" but the default is "."), so update the
personas/proactive-agent-builder.json entry for TARGET_DIR to either: (A) allow
relative paths by changing the description to "Absolute or relative path to the
proactive-agents project root (resolved from current working directory).
Defaults to '.'", or (B) make the default an absolute-path string if you must
require absolute paths; reference the TARGET_DIR key when making this change so
docs and defaults align. Ensure the wording explicitly states resolution
behavior (resolved from cwd) if you choose option A.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 8cbccf79-2e89-4e03-882f-817d06725d5b
📒 Files selected for processing (5)
packages/cli/src/local-personas.test.tspackages/cli/src/local-personas.tspackages/persona-kit/src/constants.tspackages/personas-core/personas/proactive-agent-builder.jsonpackages/workload-router/routing-profiles/default.json
Summary
proactive-agent-builderpersona that takes a natural-language agent spec ("check Reddit daily for X, summarize, DM in Slack") and scaffolds a runnable agent in a target proactive-agents project — typedagent.tsunderagents/<id>/, bootstrap-wired in the Pages Function router, env vars declared, with cron/webhook handoff lines in the output contract.parseInputsShapewas droppingoptional: truefrom input specs, so the canonical persona-maker pattern (sparsesystemPrompt: "$TASK_DESCRIPTION"+inputs.TASK_DESCRIPTION: { optional: true }) failed at launch withMissingPersonaInputErrorfor every pack-distributed persona. Added a regression test.Why now
We're productizing the proactive-agents reference site as a workforce persona so we can type one prompt and get a deployed agent. The runtime spec (cloud PR #515) is in flight; this persona deliberately scaffolds against the pre-runtime shim (Pages Function +
setEnv), matching whatagents/weekly-digest,agents/notion-to-blog, andagents/manual-chatbotship today. Whenrelay deploylands from M1, the "Bootstrap wiring" section of the sidecar swaps to that single call — no agent code changes needed.What's in the persona
systemPrompt: "$TASK_DESCRIPTION"across all three tiers, per the persona-maker convention.agentsMdContent(sidecar): runtime contract, three trigger templates with pointers to the canonicalweekly-digest/notion-to-blog/manual-chatbotreferences in the proactive-agents repo, env-injection shim, destination patterns for Slack / GitHub / Linear / Notion, idempotency and dedup conventions, output contract.best= codex/gpt-5.3-codex (reasoning high),best-value= opencode/gpt-5-nano (medium),minimum= opencode/minimax-m2.5-free (low). Default routing pins the intent tobestbecause a misshaped agent.ts ships either a dead handler or a duplicate-firing one — depth over speed.npx tsc --noEmit, read-only git, curl smoke tests; denyrm -rf,git push,npm publish,wrangler deploy.agents/**,functions/**, andwrangler.jsonc.What's in the fix
packages/cli/src/local-personas.tsparseInputsShapeonly copieddescription/env/defaultoff each spec and silently droppedoptional. The persona-kit resolver (resolvePersonaInputs) already supportsoptional: trueto substitute$NAMEas an empty string instead of throwing — but the flag never reached it for pack-installed personas. One-line plumb-through plus a regression test that asserts the flag round-trips throughloadLocalPersonas.Validation
agentworkforce agent proactive-agent-builder@best --dry-run— green (sidecar resolution, codex harness spec build, skill install all pass).agentworkforce agent proactive-agent-builder@best-value --dry-run— green.corepack pnpm run check(lint + typecheck + tests across all packages) — exits 0. 165 CLI tests pass including the new regression.node packages/personas-core/scripts/validate-personas.mjs— 15 personas ok.Test plan
corepack pnpm install && corepack pnpm -r buildcorepack pnpm run checknode packages/cli/dist/cli.js install ./packages/personas-core --overwritenode packages/cli/dist/cli.js agent proactive-agent-builder@best-value --dry-runagentworkforce agent proactive-agent-builderfrom insideproactive-agents/, then describe a cron-triggered Reddit→Slack agent in the TUI and confirm the scaffoldedagent.ts+ registry edit + env declarations land in the right places.Follow-up (not in this PR)
When the cloud M1 runtime lands
relay deploy(PR #515 /cloud-runtime-run), the sidecar's "Bootstrap wiring" section swaps from the Pages Function +setEnvshim to therelay deploy <file>call. That's a single-section edit inagentsMdContent, not a rewrite — the scaffolded agent.ts itself stays the same.🤖 Generated with Claude Code