SDK entrypoints, runtimes, and targets
One default import, an explicit `/strict` opt-out, and orthogonal runtime + target choices.
TechFabric 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.
Vite is optional build/development composition, not another runtime or deployment target. See Vite integration.
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. | defineAgent({...}) 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 defineAgent({...}) 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 { defineAgent } from '@fabric-harness/sdk';
export default defineAgent({
name: 'echo',
run: async ({ init, input }) => {
const session = await (await init()).session();
return { reply: await session.prompt<string>(String(input?.message ?? '')) };
},
});import { defineAgent, 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 defineAgent({
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 { defineAgent, schema } from '@fabric-harness/sdk/strict';
export default defineAgent({
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.
The table lists the core targets. fh build also supports aks, aca, aci, k8s, render,
databricks-app, and databricks-serving — see the full
target matrix.
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 { defineAgent } from '@fabric-harness/sdk';
+ import { defineAgent } 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 defineAgent({...}). 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
defineAgent({...})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.