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:

  • Minimalagent({...}) from @fabric-harness/sdk. Headless defaults pre-injected (stateless runtime, virtual sandbox, pi-agent-core, auto-compaction).
  • Complete — same agent({...}) 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 in examples/ with a default fixture. Clone, install, run:

git clone https://github.com/Fabric-Pro/fabric-harness
cd fabric-harness && pnpm install && pnpm build
export OPENAI_API_KEY=...
pnpm --filter @fabric-harness/example-changelog-writer run

Or in your own project:

npm install @fabric-harness/sdk @fabric-harness/cli
fabric-harness run <agent> --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 { agent } from '@fabric-harness/sdk';

export default agent<{ 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 { agent, defineCommand, schema } from '@fabric-harness/sdk';

const gh = defineCommand('gh', { env: { GH_TOKEN: process.env.GH_TOKEN } });

export default agent({
  name: 'triage',
  input: schema.object({ issueNumber: schema.number(), title: schema.string(), body: schema.string() }),
  output: schema.object({
    severity: schema.enum(['low', 'medium', 'high', 'critical']),
    labels: schema.array(schema.string()),
    suggestedComment: schema.string(),
  }),
  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.skill('triage-issue', { args: input, commands: [gh] });
  },
});

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 { agent } from '@fabric-harness/sdk';

export default agent<{ 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 { agent, defineCommand, schema } from '@fabric-harness/sdk';

const gh = defineCommand('gh', { env: { GH_TOKEN: process.env.GH_TOKEN } });

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(),
});

export default agent({
  name: 'pr-review',
  input: schema.object({ prNumber: schema.number(), repository: schema.string() }),
  output: schema.object({ findings: schema.array(finding) }),
  run: async ({ init, input }) => {
    const session = await (await init({ sandbox: 'local' })).session();
    const diff = await session.shell(`gh pr diff ${input.prNumber} -R ${input.repository}`);
    return await session.skill('review-diff', { args: { diff: diff.stdout }, commands: [gh] });
  },
});

Run it: source


3. Changelog Writer

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

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

export default agent<{ from: string; to: string }>({
  name: 'changelog', triggers: { webhook: true },
  run: async ({ init, input }) => {
    const session = await (await init({ sandbox: 'local' })).session();
    const log = await session.shell(`git log --pretty='%h %s' ${input.from}..${input.to}`);
    return { changelog: await session.prompt<string>(
      `Format these commits as a Keep-a-Changelog section. Group: Added/Changed/Fixed/Removed.\n\n${log.stdout}`,
    )};
  },
});
import { agent, schema } from '@fabric-harness/sdk';

export default agent({
  name: 'changelog',
  input: schema.object({ from: schema.string(), to: schema.string() }),
  output: schema.object({ added: schema.array(schema.string()), changed: schema.array(schema.string()), fixed: schema.array(schema.string()) }),
  run: async ({ init, input }) => {
    const session = await (await init({ sandbox: 'local' })).session();
    const log = await session.shell(`git log --pretty='%h %s' ${input.from}..${input.to}`);
    return await session.prompt(`Group commits into added/changed/fixed arrays.\n\n${log.stdout}`);
  },
});

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


4. Test Generator

Generate a Vitest spec for a TypeScript source file.

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

export default agent<{ 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 { agent, schema } from '@fabric-harness/sdk';
import { readFile } from 'node:fs/promises';

export default agent({
  name: 'testgen',
  input: schema.object({ file: schema.string(), framework: schema.enum(['vitest', 'jest']).optional() }),
  output: schema.object({ specPath: schema.string(), spec: schema.string() }),
  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}`,
    );
  },
});

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


5. Dependency Auditor

Run npm/pnpm audit and prioritize the findings.

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

export default agent<{ manager?: 'npm' | 'pnpm' }>({
  name: 'audit', triggers: { schedule: '0 9 * * *' },
  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} || true`);
    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 { agent, schema } from '@fabric-harness/sdk';

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

export default agent({
  name: 'audit',
  input: schema.object({ manager: schema.enum(['npm', 'pnpm']).optional() }),
  output: schema.object({ findings: schema.array(finding) }),
  triggers: { schedule: '0 9 * * *' },
  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} || true`);
    return await session.prompt(`Extract findings from audit JSON.\n\n${result.stdout.slice(0, 80_000)}`);
  },
});

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 { agent } from '@fabric-harness/sdk';

export default agent<{ 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 { agent, schema } from '@fabric-harness/sdk';
import type { CapabilityPolicy } from '@fabric-harness/sdk';

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 agent({
  name: 'migrate',
  input: schema.object({ description: schema.string(), dialect: schema.enum(['postgres', 'mysql']).optional() }),
  output: schema.object({ filename: schema.string(), upSql: schema.string(), downSql: schema.string() }),
  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}`,
    );
  },
});

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 { agent, fumadocsSource, withFilesystemSources, localDirectorySource } from '@fabric-harness/sdk';

export default agent<{ 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 { agent, fumadocsSource, localDirectorySource, schema, withFilesystemSources } from '@fabric-harness/sdk';

export default agent({
  name: 'apidocs',
  input: schema.object({ sourceDir: schema.string(), docsRoot: schema.string().optional() }),
  output: schema.object({ pages: schema.record(schema.string()) }),
  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 } }.');
  },
});

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 { agent } from '@fabric-harness/sdk';

export default agent<{ 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 { agent, schema } from '@fabric-harness/sdk';

export default agent({
  name: 'repro',
  input: schema.object({ issueBody: schema.string(), framework: schema.enum(['vitest', 'jest']).optional() }),
  output: schema.object({ testCode: schema.string(), runCommand: schema.string() }),
  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}`,
    );
  },
});

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 { agent } from '@fabric-harness/sdk';

export default agent<{ tag: string; previous: string }>({
  name: 'release-notes', triggers: { webhook: true },
  run: async ({ init, input }) => {
    const session = await (await init({ sandbox: 'local' })).session();
    const since = (await session.shell(`git log -1 --format=%aI ${input.previous}`)).stdout.trim();
    const prs = await session.shell(`gh pr list --state merged --search 'merged:>=${since}' --json number,title,labels --limit 200`);
    return { notes: await session.prompt<string>(
      `Write release notes for ${input.tag}. Group: Highlights/New/Improved/Fixed.\n\n${prs.stdout}`,
    )};
  },
});
import { agent, defineCommand, schema } from '@fabric-harness/sdk';

const gh = defineCommand('gh', { env: { GH_TOKEN: process.env.GH_TOKEN } });

export default agent({
  name: 'release-notes',
  input: schema.object({ tag: schema.string(), previous: schema.string() }),
  output: schema.object({
    highlights: schema.array(schema.string()),
    new: schema.array(schema.string()),
    improved: schema.array(schema.string()),
    fixed: schema.array(schema.string()),
  }),
  run: async ({ init, input }) => {
    const session = await (await init({ sandbox: 'local' })).session();
    const since = (await session.shell(`git log -1 --format=%aI ${input.previous}`)).stdout.trim();
    const prs = await session.shell(`gh pr list --state merged --search 'merged:>=${since}' --json number,title,labels --limit 200`);
    return await session.skill('group-prs', { args: { prs: prs.stdout, tag: input.tag }, commands: [gh] });
  },
});

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 { agent, fumadocsSource, withFilesystemSources } from '@fabric-harness/sdk';

export default agent<{ 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 { agent, fumadocsSource, schema, withFilesystemSources } from '@fabric-harness/sdk';

export default agent({
  name: 'runbook',
  input: schema.object({ alert: schema.string(), runbookRoot: schema.string().optional() }),
  output: schema.object({
    matchedRunbook: schema.string(),
    likelyCauses: schema.array(schema.string()),
    diagnosticCommands: schema.array(schema.string()),
  }),
  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.`);
  },
});

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


How they compose

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

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.