FabricFabricHarness
Reference

SDK entrypoints, runtimes, and targets

One default import, an explicit `/strict` opt-out, and orthogonal runtime + target choices.

Fabric Harness has one SDK with three independent choices:

  1. Entrypoint@fabric-harness/sdk (default) or @fabric-harness/sdk/strict (no defaults).
  2. Runtimestateless, inline, or temporal. Controls persistence and durability.
  3. Targetnode, temporal-worker, docker, cloudflare, foundry-hosted-agent. Where it runs.

Keeping these separate lets a 10-line webhook grow into a durable, audited, Temporal-backed background agent without changing the core init() / agent.session() / session.prompt() / session.skill() / session.task() / session.shell() primitives.

1. SDK import entrypoints

EntrypointImportUse it whenWhat's different
Default@fabric-harness/sdkAlmost always. New projects, prototypes, headless edge agents, production webhooks, support agents, even most coding agents.agent({...}) auto-injects headless defaults at every init(): runtime: 'stateless', sandbox: 'virtual', loopRuntime: pi-agent-core, compaction: { enabled: true }. Override any value to opt out per call.
Strict@fabric-harness/sdk/strictTemporal-backed durable agents (replay determinism), compliance / audit / regulated workloads, production agents that prefer no implicit behaviour.Identical call shape and exports — but agent({...}) does not inject defaults. Every option must be declared explicitly.

⚠️ Selecting runtime: 'temporal' from the default import emits a one-time runtime warning — auto-compaction is non-deterministic across Temporal replay. Use /strict for Temporal, or pass compaction: { enabled: false } explicitly.

Default — most agents
import { agent } from '@fabric-harness/sdk';

export default agent({
  name: 'echo',
  run: async ({ init, input }) => {
    const session = await (await init()).session();
    return { reply: await session.prompt<string>(String(input?.message ?? '')) };
  },
});
Default — same import, with typed schemas + policy + telemetry
import { agent, schema } from '@fabric-harness/sdk';
import type { CapabilityPolicy } from '@fabric-harness/sdk';

const policy: CapabilityPolicy = {
  commandPolicy: { allow: ['gh issue view*'], requireApproval: ['gh issue comment*'] },
};

export default agent({
  name: 'triage',
  input: schema.object({ issueNumber: schema.number(), title: schema.string() }),
  output: schema.object({ severity: schema.enum(['low','medium','high']), summary: schema.string() }),
  run: async ({ init, input }) => {
    const fabric = await init({ policy });
    const session = await fabric.session();
    return await session.prompt(`Triage issue #${input.issueNumber}: ${input.title}`);
  },
});
Strict — Temporal / compliance
import { agent, schema } from '@fabric-harness/sdk/strict';

export default agent({
  name: 'migrate',
  input: schema.object({ description: schema.string() }),
  output: schema.object({ filename: schema.string(), upSql: schema.string() }),
  run: async ({ init, input }) => {
    // Strict: every option is declared. Nothing implicit.
    const fabric = await init({
      runtime: 'temporal',
      sandbox: 'local',
      compaction: { enabled: false },   // explicit; replay-deterministic
      policy: { commandPolicy: { requireApproval: ['psql*'] } },
    });
    const session = await fabric.session();
    // ...
  },
});

2. Runtime modes

RuntimeState modelBest forTradeoff
statelessNo persisted session history. Each invocation independent.High-volume webhooks, edge/serverless, simple headless automations, prototypes.No durable artifacts, no stored approvals, no resumable history.
inlineRuns in the current process. In-memory state by default; configure a SessionStore for persistence.Local dev, Node servers, CI jobs, production web services with SQLite/Postgres/file storage.Process-local unless you configure a durable store.
temporalModel calls, shell commands, checkpoints, approvals, and child tasks run through Temporal workflows/activities.Long-running background agents, restartable jobs, approval-gated workflows, multi-day runs.Requires a Temporal server and a fh temporal-worker process.

Runtime is not tied to entrypoint. From the default import, you get runtime: 'stateless' unless you override; from /strict you must declare it. Both reach the same execution paths.

3. Deployment targets

TargetCLIWhat it means
nodefh run agent --target node / fh build --target nodeRun or build a Node HTTP/server process.
temporal-workerfh temporal-worker and fh run agent --target temporal-workerStart a worker; drive agents through Temporal workflows.
dockerfh build --target dockerPackage a Node build in a Docker-ready output.
cloudflarefh build --target cloudflareCloudflare Worker bundle.
foundry-hosted-agentfh build --target foundry-hosted-agentHosted-agent deployment artifact for Azure Foundry.

Target is where the code runs. Runtime is how sessions execute. Entrypoint is what the agent imports.

Common combinations

ScenarioEntrypointRuntimeTarget
10-line webhookdefaultstatelessnode or cloudflare
Support agent with mounted KBdefaultstateless or inlinenode or cloudflare
CI issue triagedefaultinlinenode
Production Node servicedefaultinline + SQLite/Postgres/file storenode or docker
Durable issue triage/stricttemporaltemporal-worker
Approval-gated background job/stricttemporaltemporal-worker
SOC-2 / HIPAA / FedRAMP-style audit/strictinlineany

Moving between import paths

Swap one import line. Nothing else changes:

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

The agent body stays the same. The only effect: defaults are no longer auto-injected, so any field you previously relied on a default for must be declared explicitly.

Why both exist

QuestionAnswer
Why have a /strict import at all?Replay determinism + audit clarity. Auto-compaction is non-deterministic for Temporal replay. Hidden defaults make compliance reviews harder. Strict makes every choice visible in the agent file.
Why isn't strict the default?Most agents don't need it. Asking every user to declare four options on every agent is friction.
Can I use Cloudflare R2, Docker, Daytona, OTel telemetry, custom stores from the default import?Yes. All of those are values you pass to init() or to agent({...}). They're independent of which entrypoint you import from.
Can I move from default to strict later?Trivially. Change the import, declare any options that were previously defaulting.
What about heavy provider/Temporal-client deps?Already in separate packages: @fabric-harness/temporal, @fabric-harness/cloudflare, @fabric-harness/azure, @fabric-harness/connectors. The bare SDK is small.

See also