# @fabric-harness/temporal

> [Temporal.io](https://temporal.io) durable workflow runtime adapter for [Fabric Harness](https://github.com/Fabric-Pro/fabric-harness).

Run agents as Temporal workflows. The agent code is unchanged — the runtime swaps. Sessions survive crashes, restarts, and human approval delays via Temporal's durable execution.

Status: **production-pilot ready for controlled deployments** after validating against your Temporal namespace. Main CI covers mock/unit paths, and the scheduled/manual live workflow now runs a real Temporal gRPC readiness probe plus end-to-end workflow smoke against `temporalio/auto-setup`.

## Install

```sh
npm install @fabric-harness/temporal @fabric-harness/sdk
```

## Usage

### Direct API (recommended)

Wire the Temporal runtime directly into `init()` via `sessionRuntime`. Each
`prompt`/`skill`/`task`/`shell`/`checkpoint` call routes through Temporal workflows:

```ts
import { init } from '@fabric-harness/sdk';
import { temporalSessionRuntime } from '@fabric-harness/temporal';

const fabric = await init({
  runtime: 'temporal',
  sessionRuntime: temporalSessionRuntime({
    address: 'localhost:7233',
    namespace: 'default',
    taskQueue: 'fabric-harness',
  }),
});

const session = await fabric.session();
await session.prompt('Triage issue #42');
```

### CLI-managed (alternative)

When running under `fabric-harness run <agent> --temporal`, the CLI constructs
the Temporal session for you and you only declare the runtime mode:

```ts
import { init } from '@fabric-harness/sdk/strict';

const fabric = await init({ runtime: 'temporal' });
```

Run a worker:

```sh
fh temporal-worker --task-queue fabric-harness --address localhost:7233
```

## Parity and operating model

| Capability | Temporal path |
| --- | --- |
| `session.prompt()` / `session.skill()` | Durable workflow request with model/tool activities |
| `session.task()` | Durable task request; child-workflow shape where configured |
| `session.shell()` | Shell activity through the selected sandbox |
| Checkpoint create/restore | Activity-backed when the sandbox reports support |
| Approvals | Workflow waits on approval signals; no worker thread is held |
| Cancellation/retry | Temporal cancellation and activity retry semantics, propagated where provider APIs support them |
| Worker restart | Workflow state survives; idempotency keys avoid duplicated side effects |
| Usage, cost, and attachments | Model attempts persist token/cost usage; configured stores retain attachment references across workers |

`AbortSignal` is never serialized into workflow history. The client starts the workflow, listens for
abort, cancels the handle, and surfaces `AbortError` to the caller.

Unsupported inline-only features should fail explicitly rather than silently degrading.

For tests and CI without a Temporal cluster, this package also exports a mock runtime:

```ts
import { createMockTemporalRuntime } from '@fabric-harness/temporal';

const runtime = createMockTemporalRuntime({ sessionId: 'test-1' });
```

## Remote sandbox continuity

When a remote sandbox exposes `encodeRef()`, local activities persist a
tenant-bound `sandbox_ref` before the first sandbox operation. Use a shared
lease store so only one worker can control the reconnected sandbox:

```ts
import { postgresSandboxOwnershipLeaseStore } from '@fabric-harness/node';

const activities = createLocalTemporalActivities({
  store,
  sandbox: createRemoteSandbox,
  tenantId: (sessionId) => tenantForSession(sessionId),
  sandboxOwnership: {
    store: postgresSandboxOwnershipLeaseStore(pg),
    ownerId: process.env.HOSTNAME!,
    leaseMs: 60_000,
  },
});
```

Every sandbox operation renews the lease. After worker rotation, a new worker
attaches from the persisted provider reference; the former worker is fenced
from reads, writes, commands, snapshots, and restores.

## Documentation

- [Temporal integration](https://fabric-harness.dev/docs/building/temporal)
- [Runtime modes](https://fabric-harness.dev/docs/reference/runtime-modes)

## License

Apache-2.0
