Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ agentworkforce --version
1. `./.agentworkforce/workforce/personas/*.json` — project-local
2. Configured persona source dirs. Default:
`~/.agentworkforce/workforce/personas/*.json`
3. Internal built-in system personas in `/personas/` (currently `persona-maker`)
3. Internal built-in system personas in `/personas/` (currently `persona-maker`, `persona-improver`, and `nango-function-builder`)
- Launch metadata is recorded by default for launched sessions; opt out with
`--no-launch-metadata` or `AGENTWORKFORCE_LAUNCH_METADATA=0`.
- `list` — print the catalog of personas from the cascade (cwd →
Expand Down Expand Up @@ -337,9 +337,9 @@ First-party examples:
- `@agentrelay/personas` is owned by the Relay repo and contains Relay-specific
personas such as `relay-orchestrator`.

`persona-maker` remains part of the internal built-in distribution. You do not
need to install `@agentworkforce/personas-core` before running
`agentworkforce create`.
`persona-maker`, `persona-improver`, and `nango-function-builder` remain part
of the internal built-in distribution. You do not need to install
`@agentworkforce/personas-core` before running `agentworkforce create`.

`install` is a copy utility. Use it when a project should own and edit its
persona files. `sources add <dir>` is separate: it points the cascade at a live
Expand Down Expand Up @@ -462,6 +462,8 @@ for the full mount layout and semantics.
## Personas

- `personas/persona-maker.json`
- `personas/persona-improver.json`
- `personas/nango-function-builder.json`

The built-in catalog is intentionally limited to required internal/system
personas. Optional reusable personas are distributed through persona packs:
Expand Down
10 changes: 5 additions & 5 deletions packages/workload-router/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ resolved persona and grouped install metadata. Nothing is installed,
spawned, or written to disk — run `install.commandString` yourself when
you are ready to materialize the persona's skills.

`usePersona` resolves only the internal built-in catalog (`persona-maker` /
`persona-authoring`). Optional personas such as `code-reviewer` and
`frontend-implementer` are distributed through installable persona packs and
should be loaded through the CLI/source cascade, then passed to `useSelection`
or `materializeSkillsFor`.
`usePersona` resolves only the internal built-in catalog (`persona-maker`,
`persona-improver`, and `nango-function-builder`). Optional personas such as
`code-reviewer` and `frontend-implementer` are distributed through installable
persona packs and should be loaded through the CLI/source cascade, then passed
to `useSelection` or `materializeSkillsFor`.

```ts
import { usePersona } from '@agentworkforce/workload-router';
Expand Down
1 change: 1 addition & 0 deletions packages/workload-router/routing-profiles/default.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"posthog": {"tier": "best-value", "rationale": "PostHog queries are interactive analytics lookups; best-value is sufficient and keeps latency low when chatting with the MCP server."},
"persona-authoring": {"tier": "best", "rationale": "New personas must satisfy a fixed conventions checklist (five wiring files, model-agnostic prompts, tier-isolation) before they typecheck; missing any step ships a broken routing entry, so depth over speed is the right default."},
"persona-improvement": {"tier": "best-value", "rationale": "Mining a finished session for high-leverage persona edits is constrained pattern matching against a fixed schema; best-value reasoning is sufficient and keeps the post-session prompt latency low."},
"nango-function-building": {"tier": "best-value", "rationale": "Nango function work is implementation-heavy but guided by a fixed skill, integration templates, compile, and dryrun workflow; best-value is the default unless provider behavior is ambiguous."},
"slop-audit": {"tier": "best", "rationale": "Slop auditing reads across a diff or subtree and classifies findings into a multi-category taxonomy; missed slop ships unchanged, so depth over speed is the right default."},
"api-contract-review": {"tier": "best", "rationale": "Contract review catches silent breaking changes between deployed services; missing a discriminant collision or enum widening ships incidents, so depth over speed is the right default."},
"local-stack-orchestration": {"tier": "best-value", "rationale": "Compose authoring is mostly mechanical wiring once the topology is known; best-value is sufficient when guided by explicit healthcheck and pinning rules."},
Expand Down
7 changes: 5 additions & 2 deletions packages/workload-router/routing-profiles/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"intents": {
"type": "object",
"additionalProperties": false,
"required": ["implement-frontend", "review", "architecture-plan", "requirements-analysis", "debugging", "security-review", "documentation", "verification", "test-strategy", "tdd-enforcement", "flake-investigation", "opencode-workflow-correctness", "npm-provenance", "cloud-sandbox-infra", "sage-slack-egress-migration", "sage-proactive-rewire", "cloud-slack-proxy-guard", "sage-cloud-e2e-conduction", "capability-discovery", "npm-package-compat", "posthog", "persona-authoring", "agent-relay-workflow", "slop-audit", "api-contract-review", "local-stack-orchestration", "e2e-validation", "write-integration-tests"],
"required": ["implement-frontend", "review", "architecture-plan", "requirements-analysis", "debugging", "security-review", "documentation", "verification", "test-strategy", "tdd-enforcement", "flake-investigation", "opencode-workflow-correctness", "npm-provenance", "cloud-sandbox-infra", "sage-slack-egress-migration", "sage-proactive-rewire", "cloud-slack-proxy-guard", "sage-cloud-e2e-conduction", "capability-discovery", "npm-package-compat", "posthog", "persona-authoring", "persona-improvement", "nango-function-building", "agent-relay-workflow", "slop-audit", "api-contract-review", "local-stack-orchestration", "e2e-validation", "write-integration-tests", "relay-orchestrator"],
"properties": {
"implement-frontend": { "$ref": "#/definitions/rule" },
"review": { "$ref": "#/definitions/rule" },
Expand All @@ -34,12 +34,15 @@
"npm-package-compat": { "$ref": "#/definitions/rule" },
"posthog": { "$ref": "#/definitions/rule" },
"persona-authoring": { "$ref": "#/definitions/rule" },
"persona-improvement": { "$ref": "#/definitions/rule" },
"nango-function-building": { "$ref": "#/definitions/rule" },
"agent-relay-workflow": { "$ref": "#/definitions/rule" },
"slop-audit": { "$ref": "#/definitions/rule" },
"api-contract-review": { "$ref": "#/definitions/rule" },
"local-stack-orchestration": { "$ref": "#/definitions/rule" },
"e2e-validation": { "$ref": "#/definitions/rule" },
"write-integration-tests": { "$ref": "#/definitions/rule" }
"write-integration-tests": { "$ref": "#/definitions/rule" },
"relay-orchestrator": { "$ref": "#/definitions/rule" }
}
}
},
Expand Down
3 changes: 2 additions & 1 deletion packages/workload-router/scripts/generate-personas.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ const outputFile = path.join(repoRoot, 'packages/workload-router/src/generated/p

const exportNameMap = new Map([
['persona-maker', 'personaMaker'],
['persona-improver', 'personaImprover']
['persona-improver', 'personaImprover'],
['nango-function-builder', 'nangoFunctionBuilder']
]);

/**
Expand Down
57 changes: 57 additions & 0 deletions packages/workload-router/src/generated/personas.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,63 @@
// AUTO-GENERATED by packages/workload-router/scripts/generate-personas.mjs
// Do not edit by hand. Source of truth: /personas/*.json

export const nangoFunctionBuilder = {
"id": "nango-function-builder",
"intent": "nango-function-building",
"tags": [
"implementation",
"testing",
"discovery"
],
"description": "Builds and validates Nango TypeScript actions and syncs locally, using NangoHQ integration templates, provider docs, and dryrun evidence before handoff.",
"skills": [
{
"id": "building-nango-functions-locally",
"source": "https://github.com/NangoHQ/skills#building-nango-functions-locally",
"description": "Canonical workflow for local Zero YAML TypeScript Nango actions and syncs, including checkpoint strategy, dryrun validation, generated tests, and deploy readiness."
}
],
"tiers": {
"best": {
"harness": "codex",
"model": "openai-codex/gpt-5.3-codex",
"systemPrompt": "You are a Nango function builder. Build deployable TypeScript Nango actions or syncs in the current repo using the declared Nango skill and the NangoHQ integration-templates repository as source material. For sync work, complete the checkpoint strategy gate before editing: identify the change source, checkpoint schema, how it changes provider requests or resume state, whether the provider still walks the full dataset, and the delete strategy. Confirm the project is Zero YAML TypeScript, register files in index.ts, compile with Nango, and run dryrun validation with the supplied providerConfigKey, connection id, environment, metadata, input, and checkpoint. If live dryrun is blocked by missing provider config, connection, or credentials, preserve compile/test evidence and report the exact blocker. Keep edits scoped and do not deploy unless explicitly requested.",
"harnessSettings": {
"reasoning": "high",
"timeoutSeconds": 1200,
"sandboxMode": "workspace-write",
"approvalPolicy": "on-request",
"workspaceWriteNetworkAccess": true
}
},
"best-value": {
"harness": "codex",
"model": "openai-codex/gpt-5.3-codex",
"systemPrompt": "You are a Nango function builder. Use the declared Nango skill plus NangoHQ integration-templates to add or update TypeScript Nango actions or syncs. For syncs, decide the checkpoint and deletion strategy before editing, then implement the function, register it in index.ts, run Nango compile, and run dryrun validation with the supplied connection details. If dryrun cannot reach the cloud provider config or connection, report the exact missing external state and keep local compile/test evidence.",
"harnessSettings": {
"reasoning": "medium",
"timeoutSeconds": 900,
"sandboxMode": "workspace-write",
"approvalPolicy": "on-request",
"workspaceWriteNetworkAccess": true
}
},
"minimum": {
"harness": "codex",
"model": "openai-codex/gpt-5.3-codex",
"systemPrompt": "You are a Nango function builder for small, well-scoped edits. Apply the declared Nango skill, reference NangoHQ integration-templates, keep the sync checkpoint/delete strategy explicit, update registration, run Nango compile, and attempt dryrun validation with the supplied connection details. Report precise external blockers.",
"harnessSettings": {
"reasoning": "low",
"timeoutSeconds": 600,
"sandboxMode": "workspace-write",
"approvalPolicy": "on-request",
"workspaceWriteNetworkAccess": true
}
}
},
"agentsMdContent": "# Nango Function Builder\n\nYou build or update Nango TypeScript functions with local evidence. Start by installing or materializing the `building-nango-functions-locally` skill from `https://github.com/NangoHQ/skills#building-nango-functions-locally` and follow it as the operating checklist. The declared persona skill is materialized by the workload-router dry-run/session launcher through `npx -y skills add https://github.com/NangoHQ/skills --skill building-nango-functions-locally -y`; if you must install it manually in a scratch session, the equivalent command is `npx skills add NangoHQ/skills -s building-nango-functions-locally`. Do not run either installer against the repo root.\n\nBefore writing a sync, state the sync strategy gate in your working notes: the provider change source, checkpoint schema, how the checkpoint changes requests or resume state, whether the request still walks the full dataset, and the deletion strategy. If a full refresh is necessary, cite the provider limitation that blocks changed-row checkpoints.\n\nUse `https://github.com/NangoHQ/integration-templates/` as the source of implementation patterns. Prefer copying endpoint shape, pagination style, cloud-id discovery, OAuth accessible-resource lookup, schema casing, and Nango runtime idioms from the closest template before inventing a new pattern.\n\nBefore choosing providerConfigKeys or dryrun connections, discover what already exists in Nango with the API or CLI rather than relying on memory. List provider configs/integrations, then inspect the target connection for the same environment and record the providerConfigKey, connection id, and environment you will pass to `npx nango dryrun`.\n\nConfirm the Nango project format before editing. It must be a Zero YAML TypeScript project with `.nango/`, no `nango.yaml`, function files under `<providerConfigKey>/actions` or `<providerConfigKey>/syncs`, and side-effect imports with `.js` extensions in `index.ts`.\n\nValidate in this order whenever practical: run Nango compile, run `npx nango dryrun <script> <connection-id> --validate -e <environment> --no-interactive --auto-confirm` with `--integration-id <providerConfigKey>` when script names collide, then run `npx nango dryrun ... --save`, `npx nango generate:tests`, and the package test command. If the Nango cloud provider config or supplied connection does not exist, stop the live dryrun loop and report the exact providerConfigKey, connection id, environment, and CLI error.\n\nDo not deploy unless the user explicitly asks for deployment. A PR-ready result should include the implemented function code, generated `.nango` artifacts when the repo tracks them, focused tests or generated dryrun fixtures when possible, and a concise validation summary.\n"
} as const;

export const personaImprover = {
"id": "persona-improver",
"intent": "persona-improvement",
Expand Down
19 changes: 18 additions & 1 deletion packages/workload-router/src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,14 @@ function syntheticSpec(over: Partial<PersonaSpec> = {}): PersonaSpec {

test('built-in catalog is limited to internal system personas', () => {
const builtIns = listBuiltInPersonas();
assert.deepEqual(builtIns.map((p) => p.id).sort(), ['persona-improver', 'persona-maker']);
assert.deepEqual(builtIns.map((p) => p.id).sort(), [
'nango-function-builder',
'persona-improver',
'persona-maker'
]);
assert.equal(personaCatalog['persona-authoring']?.id, 'persona-maker');
assert.equal(personaCatalog['persona-improvement']?.id, 'persona-improver');
assert.equal(personaCatalog['nango-function-building']?.id, 'nango-function-builder');
assert.equal(personaCatalog.review, undefined);
assert.ok(PERSONA_INTENTS.includes('review'));
assert.equal(routingProfiles.default.intents.review.tier, 'best-value');
Expand All @@ -93,6 +98,18 @@ test('resolves persona-maker from the default routing profile', () => {
);
});

test('resolves nango-function-builder from the default routing profile', () => {
const selection = resolvePersona('nango-function-building');
assert.equal(selection.personaId, 'nango-function-builder');
assert.equal(selection.tier, 'best-value');
assert.equal(selection.runtime.harness, 'codex');
assert.equal(selection.skills.length, 1);
assert.equal(selection.skills[0].id, 'building-nango-functions-locally');
assert.match(selection.skills[0].source, /NangoHQ\/skills#building-nango-functions-locally/);
assert.match(selection.agentsMdContent ?? '', /NangoHQ\/integration-templates/);
assert.equal(selection.runtime.harnessSettings.workspaceWriteNetworkAccess, true);
});

test('optional pack-owned intents do not resolve from the built-in catalog', () => {
assert.throws(
() => resolvePersona('review'),
Expand Down
8 changes: 5 additions & 3 deletions packages/workload-router/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { personaImprover, personaMaker } from './generated/personas.js';
import { nangoFunctionBuilder, personaImprover, personaMaker } from './generated/personas.js';
import defaultRoutingProfileJson from '../routing-profiles/default.json' with { type: 'json' };

export const HARNESS_VALUES = ['opencode', 'codex', 'claude'] as const;
Expand Down Expand Up @@ -38,6 +38,7 @@ export const PERSONA_INTENTS = [
'posthog',
'persona-authoring',
'persona-improvement',
'nango-function-building',
'agent-relay-workflow',
'slop-audit',
'api-contract-review',
Expand All @@ -47,7 +48,7 @@ export const PERSONA_INTENTS = [
'relay-orchestrator'
] as const;

export const BUILT_IN_PERSONA_INTENTS = ['persona-authoring', 'persona-improvement'] as const;
export const BUILT_IN_PERSONA_INTENTS = ['persona-authoring', 'persona-improvement', 'nango-function-building'] as const;

export type Harness = (typeof HARNESS_VALUES)[number];
export type PersonaTier = (typeof PERSONA_TIERS)[number];
Expand Down Expand Up @@ -1430,7 +1431,8 @@ function parseRoutingProfile(value: unknown, context: string): RoutingProfile {

export const personaCatalog: Partial<Record<PersonaIntent, PersonaSpec>> = {
'persona-authoring': parsePersonaSpec(personaMaker, 'persona-authoring'),
'persona-improvement': parsePersonaSpec(personaImprover, 'persona-improvement')
'persona-improvement': parsePersonaSpec(personaImprover, 'persona-improvement'),
'nango-function-building': parsePersonaSpec(nangoFunctionBuilder, 'nango-function-building')
};

export function listBuiltInPersonas(): PersonaSpec[] {
Expand Down
Loading
Loading