FabricFabricHarness

Use Cases

Ten Fabric Harness agents you can build today, across the software development lifecycle — issue triage, PR review, changelog drafting, test generation, dependency audit, migrations, API docs, bug repro, release notes, and incident runbooks. Every example shows both minimal and complete entrypoint forms.

Each use case below shows two equivalent forms:

  • MinimaldefineAgent({...}) from @fabric-harness/sdk. Headless defaults pre-injected (stateless runtime, virtual sandbox, pi-agent-core, auto-compaction).
  • Complete — same defineAgent({...}) from the same @fabric-harness/sdk import — but with typed input/output schemas, capability policy, artifacts, custom stores, providers, telemetry. No import path change required.

For Temporal-backed durable agents, compliance/audit workloads, or any case where you want zero implicit behavior, import from @fabric-harness/sdk/strict instead — same call shape, no headless defaults injected.

How to test these locally

Each agent below has a runnable scaffold and default fixture in the restricted examples/ source tree. Without repository access, create a public starter and adapt the complete definition shown on this page:

npx --yes @fabric-harness/cli@latest init my-agents
cd my-agents && npm install
npx fh run JOB_NAME --mock

For a live provider run in your own project:

npm install @fabric-harness/sdk @fabric-harness/cli
fabric-harness run JOB_NAME --payload '...'

Connecting to external knowledge bases

SourcePatternNotes
Local Markdown / MDXlocalDirectorySource('./kb')Eager mount; agent uses grep/read
Fumadocs content treefumadocsSource('./apps/docs/content/docs')Strips frontmatter
Mintlify (local)mintlifySource('./mint-docs')Includes docs.json
Mintlify (hosted MCP)connectMcpServer('mintlify', { url: '...mcp', transport: 'streamable-http' })Default transport is streamable-http
Cloudflare R2r2FilesystemSource(env.KB)From @fabric-harness/sdk/cloudflare
S3 / Azure Blobs3FilesystemSource(...) / azureBlobFilesystemSource(...)From @fabric-harness/connectors
Arbitrary HTTPhttpFilesystemSource([{ url, path }])Fetch + cache as files

Mount any of these with withFilesystemSources('virtual', [{ mountAt, source }]) and the agent gets read / grep / glob over them — no embeddings, no retrieval pipeline.


1. Issue Triage

Read a GitHub issue, classify severity, suggest labels, draft a reply.

import { defineAgent } from '@fabric-harness/sdk';

export default defineAgent<{ issueNumber: number; title: string; body: string }>({
  name: 'triage', triggers: { webhook: true },
  run: async ({ init, input }) => {
      const session = await (await init()).session();
      return await session.prompt(
        `Triage issue #${input.issueNumber}: "${input.title}". Return JSON { severity, labels[], suggestedComment }.\n\n${input.body}`,
      );
    },
});
import { defineAgent, schema } from '@fabric-harness/sdk';

const triageResult = schema.object({
  severity: schema.enum(['low', 'medium', 'high', 'critical']),
  labels: schema.array(schema.string()),
  suggestedComment: schema.string(),
});

export default defineAgent({
  name: 'triage',
  input: schema.object({ issueNumber: schema.number(), title: schema.string(), body: schema.string() }),
  output: triageResult,
  triggers: { webhook: true },
  run: async ({ init, input }) => {
    const session = await (await init({
      policy: { commandPolicy: { allow: ['gh issue view*'], requireApproval: ['gh issue comment*'] } },
    })).session();
    return await session.prompt(`Triage this issue and suggest labels and a reply.\n\n${JSON.stringify(input)}`, {
      result: triageResult,
    });
  },
});

Run it: pnpm --filter @fabric-harness/example-issue-triage-ci run:triage · source


2. PR Reviewer

Review a pull request diff for security / performance / readability and produce structured findings.

import { defineAgent } from '@fabric-harness/sdk';

export default defineAgent<{ prNumber: number; diff: string }>({
  name: 'pr-review', triggers: { webhook: true },
  run: async ({ init, input }) => {
      const session = await (await init()).session();
      return await session.prompt(
        `Review PR #${input.prNumber}. Return JSON array { file, line?, severity, category, message, suggestion? }.\n\n${input.diff}`,
      );
    },
});
import { defineAgent, schema, shellQuote } from '@fabric-harness/sdk';

const finding = schema.object({
  file: schema.string(),
  line: schema.number().optional(),
  severity: schema.enum(['info', 'low', 'medium', 'high']),
  category: schema.enum(['bug', 'security', 'performance', 'tests', 'readability']),
  message: schema.string(),
});

const reviewResult = schema.object({ findings: schema.array(finding) });

const repositorySlug = (value: string) => {
  if (!/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/.test(value))
    throw new Error(`Invalid repository slug: ${value}`);
  return value;
};

export default defineAgent({
  name: 'pr-review',
  input: schema.object({ prNumber: schema.number(), repository: schema.string() }),
  output: reviewResult,
  policy: { commandPolicy: { allow: ['gh pr diff*'] }, maxCommandTimeoutMs: 30_000 },
  run: async ({ init, input }) => {
    const session = await (await init({ sandbox: 'local' })).session();
    const repository = shellQuote(repositorySlug(input.repository));
    const diff = await session.shell(`gh pr diff ${input.prNumber} --repo ${repository}`);
    if (diff.exitCode !== 0) throw new Error(`gh pr diff failed: ${diff.stderr.trim()}`);
    return await session.prompt(`Review this pull request diff.\n\n${diff.stdout}`, {
      result: reviewResult,
    });
  },
});

Run it: source


3. Changelog Writer

Turn git log between two refs into a Keep-a-Changelog section.

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

const gitRevision = (value: string) => {
  if (!/^[A-Za-z0-9][A-Za-z0-9._/~^{}-]{0,127}$/.test(value))
    throw new Error(`Invalid Git revision: ${value}`);
  return value;
};

export default defineAgent({
  name: 'changelog',
  input: schema.object({ from: schema.string(), to: schema.string() }),
  output: schema.object({ changelog: schema.string() }),
  triggers: { webhook: true },
  init: { sandbox: 'local' },
  policy: { commandPolicy: { allow: ['git log*'] }, maxCommandTimeoutMs: 30_000 },
  run: async ({ input, shell, prompt }) => {
    const range = shellQuote(`${gitRevision(input.from)}..${gitRevision(input.to)}`);
    const log = await shell(`git log --pretty=format:'%h %s' ${range}`);
    if (log.exitCode !== 0) throw new Error(`git log failed: ${log.stderr.trim()}`);
    return { changelog: await prompt<string>(
      `Format these commits as a Keep-a-Changelog section. Group: Added/Changed/Fixed/Removed.\n\n${log.stdout}`,
    )};
  },
});
import { defineAgent, schema, shellQuote } from '@fabric-harness/sdk';

const changelog = schema.object({
  added: schema.array(schema.string()),
  changed: schema.array(schema.string()),
  fixed: schema.array(schema.string()),
  removed: schema.array(schema.string()),
});

const gitRevision = (value: string) => {
  if (!/^[A-Za-z0-9][A-Za-z0-9._/~^{}-]{0,127}$/.test(value))
    throw new Error(`Invalid Git revision: ${value}`);
  return value;
};

export default defineAgent({
  name: 'changelog',
  input: schema.object({ from: schema.string(), to: schema.string() }),
  output: changelog,
  init: { sandbox: 'local' },
  policy: { commandPolicy: { allow: ['git log*'] }, maxCommandTimeoutMs: 30_000 },
  run: async ({ input, shell, prompt }) => {
    const range = shellQuote(`${gitRevision(input.from)}..${gitRevision(input.to)}`);
    const log = await shell(`git log --pretty=format:'%h %s' ${range}`);
    if (log.exitCode !== 0) throw new Error(`git log failed: ${log.stderr.trim()}`);
    return await prompt(`Group commits into added/changed/fixed/removed arrays.\n\n${log.stdout}`, {
      result: changelog,
    });
  },
});

Run it: pnpm --filter @fabric-harness/example-changelog-writer run · source


4. Test Generator

Generate a Vitest spec for a TypeScript source file.

import { defineAgent } from '@fabric-harness/sdk';
import { readFile } from 'node:fs/promises';

export default defineAgent<{ file: string }>({
  name: 'testgen', triggers: { webhook: true },
  run: async ({ init, input }) => {
    const session = await (await init()).session();
    const source = await readFile(input.file, 'utf8');
    return { spec: await session.prompt<string>(
      `Write a thorough vitest spec for the source below. Cover happy/error/boundary paths.\n\n${source}`,
    )};
  },
});
import { defineAgent, schema } from '@fabric-harness/sdk';
import { readFile } from 'node:fs/promises';

const generatedTest = schema.object({ specPath: schema.string(), spec: schema.string() });

export default defineAgent({
  name: 'testgen',
  input: schema.object({ file: schema.string(), framework: schema.enum(['vitest', 'jest']).optional() }),
  output: generatedTest,
  run: async ({ init, input }) => {
    const session = await (await init()).session();
    const source = await readFile(input.file, 'utf8');
    return await session.prompt(
      `Write a ${input.framework ?? 'vitest'} spec. Return JSON { specPath, spec }.\n\n${source}`,
      { result: generatedTest },
    );
  },
});

Run it: pnpm --filter @fabric-harness/example-test-generator run · source


5. Dependency Auditor

Run npm/pnpm audit and prioritize the findings.

import { defineAgent } from '@fabric-harness/sdk';

export default defineAgent<{ manager?: 'npm' | 'pnpm' }>({
  name: 'audit', triggers: { schedule: '0 9 * * *' },
  run: async ({ init, input }) => {
    const session = await (await init({
      sandbox: 'local',
      policy: {
        commandPolicy: { allow: ['npm audit --json', 'pnpm audit --json'] },
        maxCommandTimeoutMs: 60_000,
      },
    })).session();
    const cmd = input.manager === 'pnpm' ? 'pnpm audit --json' : 'npm audit --json';
    const result = await session.shell(cmd);
    if (result.exitCode > 1) throw new Error(`Dependency audit failed: ${result.stderr.trim()}`);
    return { summary: await session.prompt<string>(
      `Summarize this audit JSON. List Critical/High first with package, version, recommended upgrade.\n\n${result.stdout.slice(0, 80_000)}`,
    )};
  },
});
import { defineAgent, schema } from '@fabric-harness/sdk';

const finding = schema.object({
  package: schema.string(),
  severity: schema.enum(['low', 'moderate', 'high', 'critical']),
  recommendedVersion: schema.string().optional(),
});

const auditResult = schema.object({ findings: schema.array(finding) });

export default defineAgent({
  name: 'audit',
  input: schema.object({ manager: schema.enum(['npm', 'pnpm']).optional() }),
  output: auditResult,
  triggers: { schedule: '0 9 * * *' },
  policy: {
    commandPolicy: { allow: ['npm audit --json', 'pnpm audit --json'] },
    maxCommandTimeoutMs: 60_000,
  },
  run: async ({ init, input }) => {
    const session = await (await init({ sandbox: 'local' })).session();
    const cmd = input.manager === 'pnpm' ? 'pnpm audit --json' : 'npm audit --json';
    const result = await session.shell(cmd);
    if (result.exitCode > 1) throw new Error(`Dependency audit failed: ${result.stderr.trim()}`);
    return await session.prompt(`Extract findings from audit JSON.\n\n${result.stdout.slice(0, 80_000)}`, {
      result: auditResult,
    });
  },
});

Run it: pnpm --filter @fabric-harness/example-dependency-auditor run · source


6. Schema Migration Author

Draft a forward + rollback SQL migration for a described change. Mutating apply commands are gated behind approval in the complete entrypoint example.

import { defineAgent } from '@fabric-harness/sdk';

export default defineAgent<{ description: string; dialect?: string }>({
  name: 'migrate', triggers: { webhook: true },
  run: async ({ init, input }) => {
      const session = await (await init()).session();
      return await session.prompt<string>(
        `Draft a ${input.dialect ?? 'postgres'} migration: "${input.description}". Return JSON { filename, upSql, downSql }.`,
      );
    },
});
import { defineAgent, schema } from '@fabric-harness/sdk';
import type { CapabilityPolicy } from '@fabric-harness/sdk';

const migrationResult = schema.object({
  filename: schema.string(),
  upSql: schema.string(),
  downSql: schema.string(),
});

const policy: CapabilityPolicy = {
  commandPolicy: {
    allow: ['ls migrations*', 'cat migrations/*'],
    requireApproval: ['psql*', 'pnpm prisma migrate*'],
    deny: ['rm -rf*'],
  },
  approvals: { requiredApprovals: 1, defaultTimeoutMs: 300_000, risk: 'high' },
};

export default defineAgent({
  name: 'migrate',
  input: schema.object({ description: schema.string(), dialect: schema.enum(['postgres', 'mysql']).optional() }),
  output: migrationResult,
  run: async ({ init, input }) => {
    const session = await (await init({ sandbox: 'local', policy })).session();
    const existing = await session.shell('ls migrations 2>/dev/null || true');
    return await session.prompt(
      `Draft a ${input.dialect ?? 'postgres'} migration: "${input.description}". Existing:\n${existing.stdout}`,
      { result: migrationResult },
    );
  },
});

Run it: pnpm --filter @fabric-harness/example-schema-migration run · source


7. API Docs Generator

Generate MDX docs pages for HTTP routes, matching the tone of an existing Fumadocs site.

import { defineAgent, fumadocsSource, withFilesystemSources, localDirectorySource } from '@fabric-harness/sdk';

export default defineAgent<{ sourceDir: string; docsRoot?: string }>({
  name: 'apidocs', triggers: { webhook: true },
  run: async ({ init, input }) => {
      const sandbox = withFilesystemSources('virtual', [
        { mountAt: '/workspace/api', source: localDirectorySource(input.sourceDir) },
        ...(input.docsRoot ? [{ mountAt: '/workspace/kb', source: fumadocsSource(input.docsRoot) }] : []),
      ]);
      const session = await (await init({ sandbox })).session();
      return { mdx: await session.prompt<string>(
        'Read /workspace/api with the read tool. Match tone of /workspace/kb. Return JSON { "<slug>.mdx": "<content>" }.',
      )};
    },
});
import { defineAgent, fumadocsSource, localDirectorySource, schema, withFilesystemSources } from '@fabric-harness/sdk';

const generatedDocs = schema.object({ pages: schema.record(schema.string()) });

export default defineAgent({
  name: 'apidocs',
  input: schema.object({ sourceDir: schema.string(), docsRoot: schema.string().optional() }),
  output: generatedDocs,
  run: async ({ init, input }) => {
    const sandbox = withFilesystemSources('virtual', [
      { mountAt: '/workspace/api', source: localDirectorySource(input.sourceDir) },
      ...(input.docsRoot ? [{ mountAt: '/workspace/kb', source: fumadocsSource(input.docsRoot) }] : []),
    ]);
    const session = await (await init({ sandbox })).session();
    return await session.prompt('Generate MDX page per route. Return { pages: { slug: content } }.', {
      result: generatedDocs,
    });
  },
});

Run it: pnpm --filter @fabric-harness/example-api-docs-generator run · source


8. Bug Reproducer

Convert a free-form bug report into a minimal failing test.

import { defineAgent } from '@fabric-harness/sdk';

export default defineAgent<{ issueBody: string; framework?: string }>({
  name: 'repro', triggers: { webhook: true },
  run: async ({ init, input }) => {
      const session = await (await init()).session();
      return { repro: await session.prompt<string>(
        `Produce a minimal failing ${input.framework ?? 'vitest'} test for this report:\n\n${input.issueBody}`,
      )};
    },
});
import { defineAgent, schema } from '@fabric-harness/sdk';

const reproduction = schema.object({ testCode: schema.string(), runCommand: schema.string() });

export default defineAgent({
  name: 'repro',
  input: schema.object({ issueBody: schema.string(), framework: schema.enum(['vitest', 'jest']).optional() }),
  output: reproduction,
  run: async ({ init, input }) => {
    const session = await (await init()).session();
    return await session.prompt(
      `Produce a minimal failing ${input.framework ?? 'vitest'} test. Return JSON { testCode, runCommand }.\n\n${input.issueBody}`,
      { result: reproduction },
    );
  },
});

Run it: pnpm --filter @fabric-harness/example-bug-reproducer run · source


9. Release Notes Drafter

Customer-facing release notes from merged PRs between two tags.

import { defineAgent, shellQuote } from '@fabric-harness/sdk';

const gitRevision = (value: string) => {
  if (!/^[A-Za-z0-9][A-Za-z0-9._/~^{}-]{0,127}$/.test(value))
    throw new Error(`Invalid Git revision: ${value}`);
  return value;
};

export default defineAgent<{ tag: string; previous: string }>({
  name: 'release-notes', triggers: { webhook: true },
  run: async ({ init, input }) => {
    const session = await (await init({
      sandbox: 'local',
      policy: {
        commandPolicy: { allow: ['git log*', 'gh pr list*'] },
        maxCommandTimeoutMs: 30_000,
      },
    })).session();
    const previous = shellQuote(gitRevision(input.previous));
    const sinceResult = await session.shell(`git log -1 --format=%aI ${previous}`);
    if (sinceResult.exitCode !== 0) throw new Error(`git log failed: ${sinceResult.stderr.trim()}`);
    const search = shellQuote(`merged:>=${sinceResult.stdout.trim()}`);
    const prs = await session.shell(`gh pr list --state merged --search ${search} --json number,title,labels --limit 200`);
    if (prs.exitCode !== 0) throw new Error(`gh pr list failed: ${prs.stderr.trim()}`);
    return { notes: await session.prompt<string>(
      `Write release notes for ${input.tag}. Group: Highlights/New/Improved/Fixed.\n\n${prs.stdout}`,
    )};
  },
});
import { defineAgent, schema, shellQuote } from '@fabric-harness/sdk';

const releaseNotes = schema.object({
  highlights: schema.array(schema.string()),
  new: schema.array(schema.string()),
  improved: schema.array(schema.string()),
  fixed: schema.array(schema.string()),
});

const gitRevision = (value: string) => {
  if (!/^[A-Za-z0-9][A-Za-z0-9._/~^{}-]{0,127}$/.test(value))
    throw new Error(`Invalid Git revision: ${value}`);
  return value;
};

export default defineAgent({
  name: 'release-notes',
  input: schema.object({ tag: schema.string(), previous: schema.string() }),
  output: releaseNotes,
  policy: {
    commandPolicy: { allow: ['git log*', 'gh pr list*'] },
    maxCommandTimeoutMs: 30_000,
  },
  run: async ({ init, input }) => {
    const session = await (await init({ sandbox: 'local' })).session();
    const previous = shellQuote(gitRevision(input.previous));
    const sinceResult = await session.shell(`git log -1 --format=%aI ${previous}`);
    if (sinceResult.exitCode !== 0) throw new Error(`git log failed: ${sinceResult.stderr.trim()}`);
    const search = shellQuote(`merged:>=${sinceResult.stdout.trim()}`);
    const prs = await session.shell(`gh pr list --state merged --search ${search} --json number,title,labels --limit 200`);
    if (prs.exitCode !== 0) throw new Error(`gh pr list failed: ${prs.stderr.trim()}`);
    return await session.prompt(`Write release notes for ${input.tag}.\n\n${prs.stdout}`, {
      result: releaseNotes,
    });
  },
});

Run it: pnpm --filter @fabric-harness/example-release-notes run · source


10. Incident Runbook Assistant

Match an alert to a mounted runbook and walk through diagnostic steps. Read-only — never executes.

import { defineAgent, fumadocsSource, withFilesystemSources } from '@fabric-harness/sdk';

export default defineAgent<{ alert: string; runbookRoot?: string }>({
  name: 'runbook', triggers: { webhook: true },
  run: async ({ init, input }) => {
    const sandbox = withFilesystemSources('virtual', [
      { mountAt: '/workspace/runbooks', source: fumadocsSource(input.runbookRoot ?? './runbooks') },
    ]);
    const session = await (await init({ sandbox })).session();
    return { plan: await session.prompt<string>(
      `Alert: "${input.alert}". Grep /workspace/runbooks, read the best match, return root causes + diagnostic commands. Do NOT execute.`,
    )};
  },
});
import { defineAgent, fumadocsSource, schema, withFilesystemSources } from '@fabric-harness/sdk';

const runbookPlan = schema.object({
  matchedRunbook: schema.string(),
  likelyCauses: schema.array(schema.string()),
  diagnosticCommands: schema.array(schema.string()),
});

export default defineAgent({
  name: 'runbook',
  input: schema.object({ alert: schema.string(), runbookRoot: schema.string().optional() }),
  output: runbookPlan,
  run: async ({ init, input }) => {
    const sandbox = withFilesystemSources('virtual', [
      { mountAt: '/workspace/runbooks', source: fumadocsSource(input.runbookRoot ?? './runbooks') },
    ]);
    const session = await (await init({ sandbox })).session();
    return await session.prompt(`Alert: "${input.alert}". Match a runbook and return structured causes + commands.`, {
      result: runbookPlan,
    });
  },
});

Run it: pnpm --filter @fabric-harness/example-incident-runbook run · source


How they compose

These ten aren't isolated. Real workflows combine them:

Diagram flow: graph LR; GitHub webhook leads to #1 Triage; B leads severity high #8 Bug Reproducer; C leads to Failing test in PR; D leads to #2 PR Reviewer; E leads approved Merge; F leads to #3 Changelog; G leads to Release tag; H leads to #9 Release Notes; H leads to #5 Dep Audit.
Text alternative and Mermaid source

Diagram flow: graph LR; GitHub webhook leads to #1 Triage; B leads severity high #8 Bug Reproducer; C leads to Failing test in PR; D leads to #2 PR Reviewer; E leads approved Merge; F leads to #3 Changelog; G leads to Release tag; H leads to #9 Release Notes; H leads to #5 Dep Audit.

graph LR
  A[GitHub webhook] --> B[#1 Triage]
  B -->|severity high| C[#8 Bug Reproducer]
  C --> D[Failing test in PR]
  D --> E[#2 PR Reviewer]
  E -->|approved| F[Merge]
  F --> G[#3 Changelog]
  G --> H[Release tag]
  H --> I[#9 Release Notes]
  H --> J[#5 Dep Audit]

Adjacent stages share the same Fabric Harness primitives — one agent's output schema feeds the next agent's input schema, and the policy stays consistent across the pipeline.

Picking an entrypoint

Choose the minimal entrypoint whenChoose the complete entrypoint when
Prototyping, internal tools, edge/serverless deploymentMutations need approval, you want typed I/O at the boundary
Inputs are loose JSONInputs come from a webhook with a versioned schema
One agent, one modelSkills, custom roles, custom session stores, telemetry
Cloudflare Workers, Vercel Edge, LambdaTemporal worker, Foundry Hosted, Docker fleet

You can mix: an agent using the minimal entrypoint can init({ policy }) to opt into capability gating without leaving the smaller import graph. Durability still comes from runtime settings, not the import path.