FabricFabricHarness
Building Agents

Approvals

Pause an agent until a human approves a risky action.

Approvals turn an autonomous agent into one that asks for confirmation at risky moments. The framework persists the request, suspends the relevant code path, and resumes when a human (or another system) resolves it.

Declarative routing — approvalRules

Most users want approvals tied to specific tools, not scattered through agent code. Declare the rules once in policy and the loop pauses for approval before dispatch:

const fabric = await init({
  policy: {
    toolPolicy: {
      approvalRules: [
        { pattern: 'submit_*',  audience: 'reviewer',         reason: 'Review before submission of ${name}' },
        { pattern: 'delete_*',  audience: 'project-admin',    ttlSeconds: 3600 },
        { pattern: 'finalize_*', audience: 'compliance-team' },
      ],
    },
  },
});

audience is an opaque string id. fabric-harness never decides who an audience maps to — your host application's identity layer (UI, SSO, RBAC) does that mapping. Rules also work on commandPolicy.approvalRules for bash invocations.

When the agent calls a matching tool, fabric-harness emits approval_requested with the audience id, the templated reason, and the TTL. The host UI renders the request to the right humans; on approval_granted the loop continues; on denial or TTL it throws. This is sugar over the imperative session.approval.request() API — which stays available as the escape hatch for ad-hoc cases.

Webhook subscriptions

Agents that should wake on inbound events (event bus, queue, external SaaS webhook) use defineWebhookSubscription:

import { agent, defineWebhookSubscription } from '@fabric-harness/sdk';

export default agent({
  name: 'data-validator',
  triggers: { webhook: true },
  subscriptions: [
    defineWebhookSubscription<{ recordId: string }>({
      id: 'on-record-created',
      events: ['record.created'],
      handler: async ({ payload, idempotencyKey, headers }) => {
        // ...invoke whatever your host application exposes.
      },
    }),
  ],
  async run() { /* ... */ },
});

fh server exposes each subscription at POST /agents/<agent>/subscriptions/<id>. Set FABRIC_HARNESS_WEBHOOK_SECRET to require an X-Fabric-Signature: sha256=<hmac> header on every delivery. The server dedupes by Idempotency-Key for at-least-once event buses, and emits a webhook_received event into the session log for audit.

fh subscriptions [agent] lists registered subscriptions across the workspace.

Request an approval

session.approval.request() returns true when the approver allows the action. It throws a FabricError (APPROVAL_DENIED or APPROVAL_REQUIRED) on denial or timeout — wrap in try/catch when you want to handle rejection gracefully.

import { FabricError } from '@fabric-harness/sdk';

try {
  await session.approval.request({
    reason: 'About to push changes to GitHub',
    subject: 'git push origin main',
    risk: 'high',
    timeoutMs: 24 * 60 * 60 * 1000, // 24h
  });
  await session.shell('git push origin main');
} catch (error) {
  if (error instanceof FabricError && error.code === 'APPROVAL_DENIED') {
    throw new Error(`Push blocked: ${error.message}`);
  }
  throw error;
}

The available fields are:

FieldTypeDescription
reasonstringHuman-readable reason shown to the approver.
subjectstring?Short subject line for UIs. Defaults to 'custom'.
risk'low' | 'medium' | 'high'?Drives escalation policy.
timeoutMsnumber?Override the session's default approval timeout.
onApprovalApprovalCallback?Per-call approval handler. Falls back to session/agent onApproval.

Resolve from the CLI

fh approvals <session-id> --pending
fh approve <session-id> <approval-id> --actor preetham
# or
fh reject  <session-id> <approval-id> --actor preetham --reason "Wrong branch"

Durable waits

On the Temporal worker target, the approval wait becomes a workflow signal — the agent can wait minutes, hours, or days without holding any process resources. On the inline runtime, the wait is process-local.

To make agents safe on either runtime, declare autonomy fallbacks:

await init({
  autonomy: {
    onApprovalUnavailable: 'fail',  // or 'assume-rejected' | 'assume-approved'
  },
});

Logged identities

Approvals record both the agent identity and the human actor. Combined with the two-identity actor schema (Entra Agent ID + on-behalf-of user), the audit trail answers "which agent acted, on whose behalf, who approved?"

See also