Agent Anatomy
The metadata-first shape of a Fabric Harness agent — default and strict variants of the same call.
Fabric Harness agents are metadata-first. Every agent module must default-export an agent definition created via agent({...}). The call shape is identical from both SDK entrypoints — what differs is whether headless defaults are injected at runtime.
- Default —
import { agent } from '@fabric-harness/sdk'. Injects headless defaults (runtime: 'stateless',sandbox: 'virtual',loopRuntime: pi-agent-core,compaction: { enabled: true }) on everyinit()call. The fast path for prototypes, webhooks, edge agents. - Strict —
import { agent } from '@fabric-harness/sdk/strict'. Same call shape, no defaults injected. Required for Temporal-backed durability (replay determinism) and recommended for compliance/audit workloads.
Both produce the same AgentDefinition and run identically through fh run, fh build, fh describe, and any deploy target.
Agent definition
import { agent, schema } from '@fabric-harness/sdk';
export default agent({
name: 'ask',
input: schema.object({ question: schema.string() }),
output: schema.string(),
triggers: { webhook: true },
run: async ({ init, input }) => {
const session = await (await init()).session();
return session.prompt(input.question);
},
});init() defaults are injected automatically. Override any of them by passing a value:
init({ runtime: 'inline', sandbox: 'docker' }).
import { agent, schema } from '@fabric-harness/sdk/strict';
export default agent({
name: 'ask',
input: schema.object({ question: schema.string() }),
output: schema.string(),
triggers: { webhook: true },
run: async ({ init, input }) => {
const fabric = await init({
runtime: 'temporal',
sandbox: 'local',
compaction: { enabled: false },
});
const session = await fabric.session();
return session.prompt(input.question);
},
});Every option is declared in source. Nothing implicit. Required for Temporal — auto-compaction would break replay determinism.
The function name (agent) and call shape are identical across both imports. Plain default-exported functions are intentionally rejected — requiring an agent({...}) call keeps agents discoverable and gives the CLI typed metadata.
The FabricContext
When the CLI invokes an agent it provides:
interface FabricContext<TInput = JsonObject> {
payload: TInput;
input: TInput; // validated input for metadata agents
init(options?: AgentInit): Promise<FabricAgent>;
}Use input for typed, schema-validated values inside run:
run: async ({ init, input }) => {
const fabric = await init();
const session = await fabric.session();
return session.prompt(input.question);
}init() options
Agents from either entrypoint call the same init() to construct the runtime:
const fabricAgent = await init({
id: 'agent-1',
model: 'openai/gpt-5.5',
role: 'engineer', // Markdown role file under .fabricharness/roles/
sandbox: 'local', // 'virtual' | 'empty' | 'local' | 'docker' | 'cloudflare' | factory
autonomy: {
mode: 'background',
onMissingInput: 'assume',
onApprovalUnavailable: 'fail',
onCredentialMissing: 'fail',
},
});When using the bare @fabric-harness/sdk import you typically omit runtime, sandbox, and loopRuntime — those defaults are injected. Override anything you like; defaults fill the gaps you don't set.
⚠️ Temporal users: if you set
runtime: 'temporal'from the bare import, the SDK emits a one-timeconsole.warn. Auto-compaction is non-deterministic across Temporal replay. Either switch to@fabric-harness/sdk/strictor passcompaction: { enabled: false }explicitly.
Triggers
Declare triggers inside agent({...}). The Node and Cloudflare server targets respect:
export default agent({
name: 'triage',
triggers: {
webhook: true, // POST /agents/:agent/:id
// schedule: '*/15 * * * *', // future
// cli: true, // CLI-only, default true
},
async run(ctx) {
return ctx.input;
},
});Where to go next
- SDK entrypoints, runtimes, and targets — when to swap to
/strict. - Sessions and prompts — the core call surface.
- Skills and Roles.
- Tools and Commands.
- Sandboxes.