@crewx/sdk
@crewx/sdk
CrewX SDK — load crewx.yaml and run AI agents from TypeScript/JavaScript.
Status:
0.9.0-alpha— Core API, Plugin system, and Event system are stable. Tool system coming soon.
Table of Contents
- Quick Start
- Core API
- crewx.yaml Structure
- Layout System
- Template Helpers
- Provider Bridge
- Plugin System
- Event System
- Type Reference
1. Quick Start
Install
npm install @crewx/sdk
Hello World
import { Crewx } from '@crewx/sdk';
const crewx = await Crewx.loadYaml('./crewx.yaml');
const result = await crewx.query('assistant', 'Hello! What can you do?');
if (result.ok) {
console.log(result.data); // agent's response text
} else {
console.error(result.error?.message);
}
Minimal crewx.yaml
agents:
assistant:
name: Assistant
provider: cli/claude
inline:
model: claude-sonnet-4-6
prompt: |
You are a helpful assistant.
Answer concisely.
Task Execution (with side effects)
const crewx = await Crewx.loadYaml('./crewx.yaml');
// execute() allows the agent to write files, run commands, etc.
const result = await crewx.execute('assistant', 'Refactor src/utils.ts');
console.log(`Done in ${result.meta.durationMs}ms`);
console.log(result.data);
Plugin Setup (optional)
import { FileLoggerPlugin } from '@crewx/cli/plugins/file-logger';
import { SqliteTracingPlugin } from '@crewx/cli/plugins/sqlite-tracing';
await crewx.use(new FileLoggerPlugin()); // log files → .crewx/logs/
await crewx.use(new SqliteTracingPlugin()); // task records → ~/.crewx/crewx.db
// ... run tasks ...
await crewx.close(); // flush all plugins on exit
2. Core API
Crewx.loadYaml(path, options?)
Load from a crewx.yaml file. Documents referenced in the config are loaded automatically.
const crewx = await Crewx.loadYaml('./crewx.yaml');
// With options
const crewx = await Crewx.loadYaml('./crewx.yaml', {
execPolicy: { allow: ['git *', 'npm run *'], deny: ['rm *'] },
});
Crewx.fromConfig(config, options?, projectRoot?)
Create from an already-parsed config object. Useful when you load YAML yourself or build config programmatically.
import { Crewx, CrewxProjectConfig } from '@crewx/sdk';
const config: CrewxProjectConfig = {
agents: [
{ id: 'bot', provider: 'cli/claude', inline: { prompt: 'You are a bot.' } },
],
};
const crewx = await Crewx.fromConfig(config, {}, process.cwd());
crewx.query(agentRef, message, options?)
Ask an agent a question. Read-only — the agent is expected to respond with text only.
const result = await crewx.query('assistant', 'Summarize this PR');
// With options
const result = await crewx.query('assistant', 'Translate to Korean', {
model: 'claude-opus-4-6', // override model
provider: 'cli/claude', // override provider
context: 'Additional context', // prepended to the message
});
Note:
'@assistant'형태도 동작합니다 (CLI 호환). SDK에서는 bare id가 권장됩니다.
Returns: QueryResult
crewx.execute(agentRef, message, options?)
Run a task with an agent. The agent may write files, run shell commands, etc. Uses --dangerously-skip-permissions for cli/claude.
const result = await crewx.execute('coder', 'Fix the failing tests in src/');
if (!result.ok) {
console.error('Failed:', result.error?.code, result.error?.message);
}
Returns: ExecuteResult
crewx.renderAgentPromptFull(agentId, options?)
Render the complete system prompt for an agent — layout + template expansion. Used to inspect what the agent actually receives, or to pass the prompt to another system.
const prompt = await crewx.renderAgentPromptFull('assistant');
console.log(prompt);
// With layout override
const prompt = await crewx.renderAgentPromptFull('assistant', {
layout: 'crewx/minimal',
session: { mode: 'execute', platform: 'api' },
});
crewx.registerLayout(name, template)
Register a custom layout at runtime. See Layout System.
crewx.registerLayout('my-layout', `
# {{agent.name}}
{{agent.inline.prompt}}
---
Session: {{session.mode}}
`);
const prompt = await crewx.renderAgentPromptFull('assistant', {
layout: 'my-layout',
});
crewx.agents
ReadonlyMap<string, AgentConfig> — all agents loaded from crewx.yaml.
// List all agents
for (const [id, agent] of crewx.agents) {
console.log(id, agent.provider);
}
// Check if an agent exists
if (crewx.agents.has('coder')) {
// ...
}
crewx.filterAgents(filters)
Filter agents by role, team, or provider. Supports glob patterns.
// All agents on the 'backend' team
const agents = crewx.filterAgents({ team: 'backend' });
// All Claude-based agents
const agents = crewx.filterAgents({ provider: 'cli/claude' });
// Wildcard: all cli/* providers
const agents = crewx.filterAgents({ provider: 'cli/*' });
3. crewx.yaml Structure
# ─── Agents ──────────────────────────────────────────────────
agents:
my_agent:
name: My Agent # Display name (optional)
role: Backend Developer # Role label (optional)
team: core # Team label (optional)
provider: cli/claude # Provider (required)
working_directory: . # Working dir for the agent
inline: # Inline agent definition
model: claude-sonnet-4-6 # Model override (optional)
prompt: | # System prompt (Handlebars template)
You are an expert {{agent.role}}.
Today's context: {{{documents.guidelines.content}}}
layout: crewx/minimal # Layout override for this agent (optional)
# ─── Layouts ─────────────────────────────────────────────────
layouts:
default: crewx/minimal # Project-level default layout
# ─── Documents ───────────────────────────────────────────────
documents:
guidelines:
path: ./docs/guidelines.md # Loaded at startup, available as documents.guidelines
api_spec:
path: ./docs/api.md
Multiple providers
agents:
polyglot:
provider: # Array = first is primary, rest are fallbacks (future)
- cli/claude
- cli/gemini
4. Layout System
Layouts wrap the agent's raw prompt with structure — identity blocks, session info, available tools, etc.
Built-in Layouts
| ID | Description |
|---|---|
crewx/default |
Full structured layout (identity + session + prompt) |
crewx/minimal |
Minimal wrapper — just the agent prompt |
Resolution Priority
When calling renderAgentPromptFull(), the layout is resolved in this order (first match wins):
options.layout— call-site overrideagent.inline.layout— per-agent definition increwx.yamlconfig.layouts.default— project-level defaultcrewx/default— SDK built-in fallback
Custom Layout via registerLayout()
crewx.registerLayout('compact', `
## {{agent.name}} ({{agent.role}})
{{agent.inline.prompt}}
`);
const prompt = await crewx.renderAgentPromptFull('assistant', {
layout: 'compact',
});
Inline Layout in crewx.yaml
agents:
assistant:
provider: cli/claude
inline:
prompt: You are helpful.
layout:
id: crewx/default
props:
show_skills: false # Pass props to the layout template
Inline Template String
const prompt = await crewx.renderAgentPromptFull('assistant', {
layout: { template: 'SYSTEM: {{agent.inline.prompt}}' },
});
5. Template Helpers
The inline.prompt field in crewx.yaml is a Handlebars template. These helpers are available:
P0 Helpers (Core)
| Helper | Usage | Description |
|---|---|---|
exec |
{{exec "git log --oneline -5"}} |
Run shell command and inline output |
include |
{{include someVar}} |
Include a string variable as-is (no escaping) |
fenced_code |
{{fenced_code content lang="ts"}} |
Wrap content in Markdown code block |
Condition Helpers
{{#if (eq agent.team "backend")}}Backend mode{{/if}}
{{#if (and featureA featureB)}}Both enabled{{/if}}
Available: eq, ne, and, or, not, contains
Utility Helpers
| Helper | Description |
|---|---|
truncate text len |
Truncate string to N chars |
length array |
Array/string length |
escapeHandlebars text |
Escape {{ in content |
formatFileSize bytes |
1048576 → 1 MB |
formatTimestamp ms |
Unix ms → readable date |
Document Access in Templates
Documents defined in crewx.yaml are automatically available:
# Inline the full document
{{{documents.guidelines.content}}}
# Access metadata
Path: {{documents.guidelines.path}}
exec Security Policy
Control which shell commands are allowed in templates:
const crewx = await Crewx.loadYaml('./crewx.yaml', {
execPolicy: {
allow: ['git *', 'npm run *', 'cat *'],
deny: ['rm *', 'curl *'],
},
});
Or set it in crewx.yaml:
settings:
template:
exec:
allow:
- "git *"
- "npm run *"
deny:
- "rm *"
Glob syntax: * matches within a segment, ** matches across path segments.
6. Provider Bridge
Each agent has a provider field that tells the SDK which AI backend to use.
Supported Providers
| Provider | CLI Command | Notes |
|---|---|---|
cli/claude |
claude |
Claude Code CLI |
cli/gemini |
gemini |
Gemini CLI |
cli/copilot |
gh copilot suggest |
GitHub Copilot |
cli/codex |
codex |
OpenAI Codex CLI |
Coming Soon:
api/claude,api/openai— direct API providers without CLI dependency.
Provider Override at Call Site
// Use a different provider for one call
const result = await crewx.query('assistant', 'Hello', {
provider: 'cli/gemini',
model: 'gemini-2.0-flash',
});
query vs execute Mode
query() |
execute() |
|
|---|---|---|
| Intent | Read-only Q&A | Task with side effects |
| cli/claude flags | -p --output-format stream-json --verbose |
+ --dangerously-skip-permissions |
| Use when | Asking questions, generating text | Writing files, running commands |
7. Plugin System
Plugins extend CrewxPlugin from @crewx/sdk and attach event listeners in attach(). Register with crewx.use(plugin) and release resources by calling crewx.close().
Lifecycle
await crewx.use(plugin); // calls plugin.attach(crewx) — subscribe events, open DB, etc.
await crewx.close(); // calls plugin.detach(crewx) on all plugins in LIFO order
Same plugin instance registered twice is silently ignored.
Built-in Plugins (@crewx/cli)
| Plugin | Import path | Storage |
|---|---|---|
FileLoggerPlugin |
@crewx/cli/plugins/file-logger |
.crewx/logs/{ts}_{traceId}.log (one file per task) |
SqliteTracingPlugin |
@crewx/cli/plugins/sqlite-tracing |
~/.crewx/crewx.db — tasks table |
The
crewxCLI auto-attaches both plugins for every run. No setup needed when using the CLI directly.
Custom Plugin
import { CrewxPlugin } from '@crewx/sdk';
import type { Crewx } from '@crewx/sdk';
class CostTrackerPlugin extends CrewxPlugin {
readonly name = 'cost-tracker';
attach(crewx: Crewx) {
crewx.on('task:end', (e) => {
if (e.costUsd) console.log(`[cost] ${e.agentRef}: $${e.costUsd.toFixed(4)}`);
});
}
// detach() is optional — base class no-op is sufficient if no cleanup needed
}
await crewx.use(new CostTrackerPlugin());
8. Event System
The Crewx class extends TypedEventEmitter. Events are emitted automatically during query() and execute().
Event Catalog
| Event | When | Key Payload Fields |
|---|---|---|
task:start |
query()/execute() begins |
traceId, agentRef, mode, pid, model, provider, message, timestamp |
task:end |
Call completes (success or failure) | traceId, agentRef, durationMs, result, error, inputTokens, outputTokens, costUsd, model |
task:output |
Each line of provider output | traceId, agentRef, output, level (stdout|stderr|info) |
All events share traceId (format: tsk_XXXXXXXX) and timestamp inherited from BaseEvent.
crewx.on(event, listener) → UnsubscribeFn
import type { TaskEndEvent } from '@crewx/sdk';
const unsub = crewx.on('task:end', (e: TaskEndEvent) => {
console.log(`${e.agentRef} finished in ${e.durationMs}ms | tokens: ${e.inputTokens}+${e.outputTokens}`);
});
// Remove listener when no longer needed
unsub();
crewx.once(event, listener) fires exactly once and auto-unsubscribes.
Best practice: Subscribe inside
Plugin.attach()so listeners are automatically removed bycrewx.close(). Directcrewx.on()calls are fine for one-off use but require manualunsub()calls.
9. Type Reference
CrewxOptions
interface CrewxOptions {
workspaceRoot?: string;
platform?: 'cli' | 'slack' | 'api';
execPolicy?: ExecPolicy; // Allowed/denied shell commands in {{exec}}
}
QueryOptions / ExecuteOptions
interface QueryOptions {
model?: string; // Override model (e.g. 'claude-opus-4-6')
provider?: string; // Override provider (e.g. 'cli/gemini')
context?: string; // Extra context prepended to message
metadata?: Record<string, unknown>;
}
// ExecuteOptions has the same shape
QueryResult / ExecuteResult
interface QueryResult {
ok: boolean;
data: string; // Agent's response text
error?: {
code: string; // 'AGENT_NOT_FOUND' | 'PROVIDER_ERROR' | 'QUERY_FAILED'
message: string;
};
meta: {
agentId: string;
provider: string;
model?: string;
durationMs: number;
};
}
// ExecuteResult has the same shape
AgentConfig
interface AgentConfig {
id: string;
name?: string;
role?: string;
team?: string;
provider: string | string[];
working_directory?: string;
description?: string;
inline?: {
model?: string;
system_prompt?: string;
prompt?: string;
layout?: string | { id: string; props?: Record<string, unknown> };
};
}
ExecPolicy
interface ExecPolicy {
allow: string[]; // Glob patterns for allowed commands
deny: string[]; // Glob patterns for denied commands (takes precedence)
}
Coming Soon
- Tool System — attach custom tools/functions to agents (§3 of design spec)
api/*Providers — direct API calls without CLI dependency
Requirements
- Node.js >= 20.19.0
- At least one CLI provider installed (e.g.
npm i -g @anthropic-ai/claude-code)