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:
- Entrypoint —
@fabric-harness/sdk(default) or@fabric-harness/sdk/strict(no defaults). - Runtime —
stateless,inline, ortemporal. Controls persistence and durability. - Target —
node,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
| Entrypoint | Import | Use it when | What's different |
|---|---|---|---|
| Default | @fabric-harness/sdk | Almost 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/strict | Temporal-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/strictfor Temporal, or passcompaction: { enabled: false }explicitly.
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 ?? '')) };
},
});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}`);
},
});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
| Runtime | State model | Best for | Tradeoff |
|---|---|---|---|
stateless | No persisted session history. Each invocation independent. | High-volume webhooks, edge/serverless, simple headless automations, prototypes. | No durable artifacts, no stored approvals, no resumable history. |
inline | Runs 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. |
temporal | Model 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
| Target | CLI | What it means |
|---|---|---|
node | fh run agent --target node / fh build --target node | Run or build a Node HTTP/server process. |
temporal-worker | fh temporal-worker and fh run agent --target temporal-worker | Start a worker; drive agents through Temporal workflows. |
docker | fh build --target docker | Package a Node build in a Docker-ready output. |
cloudflare | fh build --target cloudflare | Cloudflare Worker bundle. |
foundry-hosted-agent | fh build --target foundry-hosted-agent | Hosted-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
| Scenario | Entrypoint | Runtime | Target |
|---|---|---|---|
| 10-line webhook | default | stateless | node or cloudflare |
| Support agent with mounted KB | default | stateless or inline | node or cloudflare |
| CI issue triage | default | inline | node |
| Production Node service | default | inline + SQLite/Postgres/file store | node or docker |
| Durable issue triage | /strict | temporal | temporal-worker |
| Approval-gated background job | /strict | temporal | temporal-worker |
| SOC-2 / HIPAA / FedRAMP-style audit | /strict | inline | any |
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
| Question | Answer |
|---|---|
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
- Agent anatomy — the
agent({...})shape. - Runtime modes —
statelessvsinlinevstemporalin detail. - HTTP server —
/agents/:name/:idREST + SSE. - Compaction — when and how auto-compaction triggers.
- Capability matrix — what each piece can do.