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:
- Minimal —
agent({...})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/sdkimport — but with typedinput/outputschemas, capabilitypolicy, 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/strictinstead — 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 runOr in your own project:
npm install @fabric-harness/sdk @fabric-harness/cli
fabric-harness run <agent> --payload '...'Connecting to external knowledge bases
| Source | Pattern | Notes |
|---|---|---|
| Local Markdown / MDX | localDirectorySource('./kb') | Eager mount; agent uses grep/read |
| Fumadocs content tree | fumadocsSource('./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 R2 | r2FilesystemSource(env.KB) | From @fabric-harness/sdk/cloudflare |
| S3 / Azure Blob | s3FilesystemSource(...) / azureBlobFilesystemSource(...) | From @fabric-harness/connectors |
| Arbitrary HTTP | httpFilesystemSource([{ 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 when | Choose the complete entrypoint when |
|---|---|
| Prototyping, internal tools, edge/serverless deployment | Mutations need approval, you want typed I/O at the boundary |
| Inputs are loose JSON | Inputs come from a webhook with a versioned schema |
| One agent, one model | Skills, custom roles, custom session stores, telemetry |
| Cloudflare Workers, Vercel Edge, Lambda | Temporal 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.