FabricFabricHarness
Building Agents

Agent Anatomy

The metadata-first shape of a Fabric Harness agent.

Fabric Harness agents are metadata-first. Every agent module must default-export agent({...}) from @fabric-harness/sdk.

Agent definition

import { agent, schema } from '@fabric-harness/sdk';

export default agent({
  name: 'ask',
  description: 'Answers a question using the configured model.',
  input: schema.object({
    question: schema.string().describe('Question to answer'),
  }),
  output: schema.string(),
  model: 'mock/test-model',
  triggers: { webhook: true },
  run: async ({ init, input }) => {
    const fabricAgent = await init();
    const session = await fabricAgent.session();
    return session.prompt(input.question);
  },
});

The framework will:

  1. Validate input against the schema before calling run.
  2. Validate the return value against the output schema.
  3. Surface name, description, model, triggers, and schemas to fh describe.
  4. Apply declared triggers when deployed.

Plain default-exported functions are intentionally rejected. Requiring agent({...}) keeps agents discoverable, gives the CLI typed input/output metadata, and avoids hidden trigger/config conventions.

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

const fabricAgent = await init({
  id: 'agent-1',
  model: 'openai/gpt-5.5',
  role: 'engineer',                 // Markdown role file under .fabricharness/roles/
  sandbox: 'local',                 // 'empty' | 'local' | 'docker' | 'cloudflare' | factory
  autonomy: {
    mode: 'background',             // 'background' | 'interactive'
    onMissingInput: 'assume',
    onApprovalUnavailable: 'fail',
    onCredentialMissing: 'fail',
  },
});

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