Roles are first-class agents that can be registered, composed via dependency metadata, planned by the execution engine, and executed by the orchestrator or runtime.
Enterprise role orchestration • workflow automation • deterministic agent framework — CEO, CTO, CFO, COO, CMO, AEO, Architect, Planner, Analyst, Reviewer, Executor
CEO ◄────────────────┬──────────┐
│ │
CTO ◄── CEO CFO ◄── CEO COO ◄── CEO, CFO
│ │
AEO ◄── CTO, COO CMO ◄── CEO, COO
│
Architect ◄── CTO, AEO
│
Planner ◄── Architect, COO
│
Analyst ◄── Planner, CFO
│
Reviewer ◄── Analyst, Planner
│
Executor ◄── Reviewer, AEO
@sholder/roles targets Node.js 18+ and ships as an ES module with CJS and type declarations in the published package.
npm install @sholder/rolesEvery role follows the same lifecycle:
init(input)validates the incomingRoleInput.execute(input)runs the agent and returns a typedRoleOutput.validate(output)checks the completed output before it is accepted.
The shared input and output shapes are:
const roleInput = {
context: {},
params: {},
timestamp: Date.now(),
traceId: 'trace-123',
};
const roleOutput = {
roleId: 'CEO',
result: {},
status: 'completed',
durationMs: 0,
timestamp: Date.now(),
traceId: 'trace-123',
};The public API is centered on these classes and helpers:
import {
BaseRoleAgent,
CEOAgent,
createDefaultAgents,
RoleRegistry,
ExecutionEngine,
Orchestrator,
RoleRuntime,
generateTraceId,
mergeContext,
} from '@sholder/roles';For most consumers, RoleRuntime is the simplest entry point because it ships with all 11 default agents pre-registered.
import { RoleRuntime } from '@sholder/roles';
const runtime = new RoleRuntime();
const report = await runtime.executeAll({
context: {},
params: { vision: 'enterprise', budget: 10_000_000 },
});
console.log(report.status); // completed
console.log(report.outputs.get('Executor')?.result);The following sections describe each built-in role, its dependencies, and the result fields it produces.
- Dependency metadata: none.
- Purpose: defines strategy and approval direction.
- Common inputs:
params.vision,params.objectives. - Result fields:
strategy,approved,objectives,mandate,quarter.
import { CEOAgent, generateTraceId } from '@sholder/roles';
const report = await new CEOAgent().execute({
context: {},
params: { vision: 'growth', objectives: ['revenue'] },
timestamp: Date.now(),
traceId: generateTraceId(),
});- Dependency metadata:
CEO. - Purpose: converts strategy into a technology roadmap.
- Common inputs:
context.strategy,params.stack. - Result fields:
techRoadmap,stack,standards,scalabilityTarget.
import { CTOAgent, generateTraceId } from '@sholder/roles';
const output = await new CTOAgent().execute({
context: { strategy: 'strategic-plan-growth' },
params: { stack: ['typescript', 'node'] },
timestamp: Date.now(),
traceId: generateTraceId(),
});
console.log(output.result.techRoadmap);- Dependency metadata:
CEO. - Purpose: approves budgets and financial strategy.
- Common inputs:
context.strategy,params.budget. - Result fields:
approvedBudget,fiscalStrategy,riskTolerance,burnRateMonthly,runway.
import { CFOAgent, generateTraceId } from '@sholder/roles';
const output = await new CFOAgent().execute({
context: { strategy: 'strategic-plan-growth' },
params: { budget: 1_200_000 },
timestamp: Date.now(),
traceId: generateTraceId(),
});
console.log(output.result.burnRateMonthly);- Dependency metadata:
CEO,CFO. - Purpose: turns financial approval into operating assumptions.
- Common inputs:
context.strategy,context.approvedBudget. - Result fields:
operatingModel,efficiency,headcount,processFramework,kpis.
import { COOAgent, generateTraceId } from '@sholder/roles';
const output = await new COOAgent().execute({
context: {
strategy: 'strategic-plan-growth',
approvedBudget: 500_000,
},
params: {},
timestamp: Date.now(),
traceId: generateTraceId(),
});
console.log(output.result.headcount);- Dependency metadata:
CEO,COO. - Purpose: shapes brand and go-to-market execution.
- Common inputs:
context.strategy,params.segment. - Result fields:
brandStrategy,targetSegment,channels,cac,ltv.
import { CMOAgent, generateTraceId } from '@sholder/roles';
const output = await new CMOAgent().execute({
context: { strategy: 'strategic-plan-growth' },
params: { segment: 'smb' },
timestamp: Date.now(),
traceId: generateTraceId(),
});
console.log(output.result.channels);- Dependency metadata:
CTO,COO. - Purpose: coordinates autonomous agent pipelines and execution contracts.
- Common inputs:
context.techRoadmap,context.operatingModel. - Result fields:
agentPipeline,concurrency,schedulerMode,contractVersion,agentCount.
import { AEOAgent, generateTraceId } from '@sholder/roles';
const output = await new AEOAgent().execute({
context: {
techRoadmap: 'roadmap-strategic-plan-growth',
operatingModel: 'ops-strategic-plan-growth',
},
params: {},
timestamp: Date.now(),
traceId: generateTraceId(),
});
console.log(output.result.agentCount);- Dependency metadata:
CTO,AEO. - Purpose: designs system topology and architectural constraints.
- Common inputs:
context.techRoadmap,context.agentPipeline. - Result fields:
systemDesign,topology,pipelineArch,patterns,slaMs.
import { ArchitectAgent, generateTraceId } from '@sholder/roles';
const output = await new ArchitectAgent().execute({
context: {
techRoadmap: 'roadmap-strategic-plan-growth',
agentPipeline: 'pipeline-roadmap-strategic-plan-growth-ops-strategic-plan-growth',
},
params: {},
timestamp: Date.now(),
traceId: generateTraceId(),
});
console.log(output.result.patterns);- Dependency metadata:
Architect,COO. - Purpose: converts architecture into an executable plan.
- Common inputs:
context.systemDesign,context.operatingModel. - Result fields:
executionPlan,phases,sprintCount,milestones,criticalPath.
import { PlannerAgent, generateTraceId } from '@sholder/roles';
const output = await new PlannerAgent().execute({
context: {
systemDesign: 'arch-roadmap-strategic-plan-growth',
operatingModel: 'ops-strategic-plan-growth',
},
params: {},
timestamp: Date.now(),
traceId: generateTraceId(),
});
console.log(output.result.executionPlan);- Dependency metadata:
Planner,CFO. - Purpose: produces KPI-oriented analysis and forecast data.
- Common inputs:
context.executionPlan,context.approvedBudget. - Result fields:
analysisReport,roi,variance,forecast,dataPoints.
import { AnalystAgent, generateTraceId } from '@sholder/roles';
const output = await new AnalystAgent().execute({
context: {
executionPlan: 'plan-arch-roadmap-strategic-plan-growth-ops-strategic-plan-growth',
approvedBudget: 1_000_000,
},
params: {},
timestamp: Date.now(),
traceId: generateTraceId(),
});
console.log(output.result.forecast);- Dependency metadata:
Analyst,Planner. - Purpose: applies quality gates before release.
- Common inputs:
context.analysisReport,context.executionPlan. - Result fields:
reviewId,approved,qualityScore,findings,gatesPassed.
import { ReviewerAgent, generateTraceId } from '@sholder/roles';
const output = await new ReviewerAgent().execute({
context: {
analysisReport: 'analysis-plan-arch-roadmap-strategic-plan-growth-ops-strategic-plan-growth',
executionPlan: 'plan-arch-roadmap-strategic-plan-growth-ops-strategic-plan-growth',
},
params: {},
timestamp: Date.now(),
traceId: generateTraceId(),
});
console.log(output.result.approved);- Dependency metadata:
Reviewer,AEO. - Purpose: materializes the approved plan.
- Common inputs:
context.reviewId,context.agentPipeline. - Result fields:
executionId,pipeline,deployed,checksum,artifactsProduced.
import { ExecutorAgent, generateTraceId } from '@sholder/roles';
const output = await new ExecutorAgent().execute({
context: {
reviewId: 'review-analysis-plan-arch-roadmap-strategic-plan-growth-ops-strategic-plan-growth',
agentPipeline: 'pipeline-roadmap-strategic-plan-growth-ops-strategic-plan-growth',
},
params: {},
timestamp: Date.now(),
traceId: generateTraceId(),
});
console.log(output.result.deployed);Use RoleRegistry and Orchestrator when you want direct control over which agents are available and which subset should execute.
import { Orchestrator, RoleRegistry, createDefaultAgents } from '@sholder/roles';
const registry = new RoleRegistry();
for (const agent of createDefaultAgents().values()) {
registry.register(agent);
}
const orchestrator = new Orchestrator(registry, {
maxConcurrency: 4,
timeoutMs: 30_000,
retryLimit: 0,
strict: true,
});
const report = await orchestrator.run(['CEO', 'CTO', 'CFO'], {
context: {},
params: { vision: 'platform', budget: 5_000_000 },
});ExecutionEngine resolves dependencies, then groups ready nodes into deterministic stages.
import { ExecutionEngine, RoleRegistry, createDefaultAgents } from '@sholder/roles';
const registry = new RoleRegistry();
for (const agent of createDefaultAgents().values()) registry.register(agent);
const engine = new ExecutionEngine(registry);
const graph = engine.buildGraph(['CEO', 'CTO', 'AEO'], { context: {}, params: {} });
const plan = engine.buildPlan(graph);
console.log(plan.stages);The orchestrator carries role output forward by merging output.result into the accumulated context.
import { mergeContext } from '@sholder/roles';
const next = mergeContext({ strategy: 'default' }, { approvedBudget: 1_000_000 });Pass a stable traceId when you want to correlate all outputs from a single run.
import { Orchestrator, RoleRegistry, CEOAgent, generateTraceId } from '@sholder/roles';
const registry = new RoleRegistry();
registry.register(new CEOAgent());
const orchestrator = new Orchestrator(registry);
const traceId = generateTraceId();
const report = await orchestrator.run(['CEO'], { context: {}, params: {} }, traceId);The package exposes a fixed bundled RoleId union for the built-in roles, but the execution contract itself is extensible through BaseRoleAgent. If you need application-specific roles, define a thin adapter layer in your app that maps your local role identifiers into the same registry/orchestrator flow.
import { BaseRoleAgent } from '@sholder/roles';
class CustomAgent extends BaseRoleAgent {
metadata = {
id: 'CEO',
name: 'Custom Agent',
version: '1.0.0',
description: 'Application-specific extension point',
dependencies: [],
priority: 10,
};
run(input) {
return Promise.resolve({
customOutput: true,
trace: input.traceId,
});
}
}Keep custom roles deterministic, side-effect free, and explicit about dependencies so the execution plan stays predictable.
Q: What is @sholder/roles in one line?
- A: A deterministic role orchestration library with built-in enterprise agents and an engine/orchestrator/runtime to register, plan, and execute dependent roles.
Q: How do I install and which Node version is required?
- A:
npm install @sholder/roles— requires Node.js 18+; package ships as ESM with a CJS build and type declarations.
Q: Quickstart: run all default roles with minimal code?
- A: Create
new RoleRuntime()and callexecuteAll({ context: {}, params: {} })for a zero-config run with bundled agents.
Q: I only want a subset of roles — how?
- A: Use
RoleRegistry+Orchestrator: register chosen agents and callorchestrator.run(['CEO','CTO'], input)to control scope and concurrency.
Q: How are dependencies resolved and executed deterministically?
- A: Each agent exposes
metadata.dependencies;ExecutionEnginetopologically sorts nodes into deterministic stages and the orchestrator executes ready stages in order.
Q: How does context merging work across roles?
- A: The orchestrator merges each
output.resultinto accumulated context usingmergeContext, enabling downstream roles to read required values.
Q: How to author a custom role safely?
- A: Extend
BaseRoleAgent, declaremetadata(id, dependencies), keeprun/init/validatedeterministic and side-effect free, then register withRoleRegistry.
Q: What about TypeScript typings and RoleId union?
- A: The package exports types; built-in
RoleIdcovers bundled roles — for custom ids, keep local type adapters or widen your application types.
Q: Can I use this library in both ESM and CJS codebases?
- A: Yes — prefer ESM imports in modern projects; use the CJS build if your environment requires
require().
Q: How to test roles and orchestration behavior?
- A: Unit-test individual agents by mocking inputs/outputs; integration-test
ExecutionEngine/Orchestratorwith a smallRoleRegistryand deterministic mock agents (seetests/).
Q: How to tune concurrency, timeouts, and retries?
- A: Configure
OrchestratorwithmaxConcurrency,timeoutMs, andretryLimit; togglestrictto control abort-on-failure behavior.
Q: Any performance best practices?
- A: Avoid CPU-heavy synchronous work in agents, batch large payloads, and size
maxConcurrencyto the runtime's throughput; stage grouping reduces unnecessary waits.
Q: How to bundle for browser or Deno usage?
- A: The library is Node-first; for browser/Deno, create a thin adapter exposing the minimal runtime API and bundle with Rollup/Esbuild, shimming Node APIs as needed.
Q: How are side-effects and secrets handled?
- A: Keep side-effects explicit and audit them; do not store secrets in plain
context/params— use encrypted stores or environment configs.
Q: What about versioning and API compatibility?
- A: Follow semver — breaking changes are released as major versions; pin to a tested minor and read the changelog before upgrading.
Q: Troubleshooting tip: missing/duplicate role errors?
- A:
RoleErrorcommonly means a registration or metadata issue — ensure eachmetadata.idis unique and all declared dependencies are registered.
Q: Where to get help or contribute?
- A: Open an issue or PR in the repository and follow
CONTRIBUTING.mdfor guidelines.
Q: What should a basic metadata.json include for role execution?
- A: Include execution targets, shared params, and an initial context object. Keep it small and deterministic.
{
"roles": [
"CEO",
"CTO",
"CFO",
"COO",
"AEO",
"Architect",
"Planner",
"Analyst",
"Reviewer",
"Executor"
],
"params": {
"vision": "enterprise-platform",
"budget": 5000000
},
"context": {},
"options": {
"maxConcurrency": 4,
"strict": true,
"timeoutMs": 30000,
"retryLimit": 0
}
}Q: How do I run @sholder/roles using values from metadata.json?
- A: Read the JSON, create a
RoleRegistry+Orchestrator(orRoleRuntime), then passcontextandparamsintorun/executeAll.
import { readFile } from 'node:fs/promises';
import { Orchestrator, RoleRegistry, createDefaultAgents } from '@sholder/roles';
const metadata = JSON.parse(await readFile('./metadata.json', 'utf8'));
const registry = new RoleRegistry();
for (const agent of createDefaultAgents().values()) registry.register(agent);
const orchestrator = new Orchestrator(registry, metadata.options ?? {});
const report = await orchestrator.run(metadata.roles, {
context: metadata.context ?? {},
params: metadata.params ?? {},
});
console.log(report.status);Q: Can metadata.json define only a subset of roles?
- A: Yes. Provide a subset in
roles; dependencies are resolved by the engine. Ensure all required dependency agents are registered.
Q: Should I store secrets in metadata.json?
- A: No. Keep secrets in environment variables or a secret manager; inject into
paramsat runtime only when required.
Q: How do I validate metadata.json before running orchestration?
- A: Check JSON parsing, ensure
rolesis a non-empty array, and validate option types (maxConcurrency,timeoutMs,retryLimit,strict) before execution.
Q: What common mistakes break metadata-driven runs?
- A: Invalid role IDs, missing dependencies in registry, malformed JSON, and passing non-object
context/params.
RoleErrorusually means a role was missing, duplicated, or given invalid lifecycle input.CyclicDependencyErrormeans your dependency metadata forms a loop and the engine cannot produce a valid plan.OrchestrationErrorcovers deadlocks, timeouts, and strict-mode failures during execution.RoleRuntimealready registers the bundled agents, so use it when you want the fastest possible path to a complete run.- If you need reproducible logs or downstream correlation, pass your own
traceIdinstead of relying on the generated value. - In strict mode, a single rejected role aborts the orchestration; set
strict: falseif you want partial progress to continue.
@sholder/roles is maintained by Girish Kor.
Repository: girish-kor/sholder-roles