Skip to content

anikamin940/hitl-agent-ts

Repository files navigation

hitl-agent-ts

Human-in-the-loop workflow orchestrator for TypeScript — chain steps, pause on approval gates, then continue when a human approves or rejects. Includes in-memory, SQLite (sql.js, file-backed), and PostgreSQL stores; optional Slack / HTTP webhook notifications; a small REST API; a CLI; and Vitest helpers.

TypeScript License: MIT Node.js

Features

Feature Description
Approval gates Pause until approve or reject (optional comment)
Timeouts e.g. timeout: '30m' with onTimeout: reject, escalate, or retry
Multi-approver requiredApprovers: n — counts distinct approve decisions
Persistence MemoryStore, SQLiteStore, PostgresStore
Notifications Console default; Slack incoming webhook; custom HTTP webhook
Testing TestOrchestrator + Vitest

Requirements

  • Node.js 20+ (ESM; global fetch in supported Node versions)
  • Optional: Docker (see docker-compose.yml) for PostgreSQL

Install

cd hitl-agent-ts
npm install
npm run build

npm run build produces dist/, which is required for npm start, npm run start:api, and for imports that resolve to dist/ (including the hitl-agent-ts/hitl export in package.json).

Copy .env.example to .env when running the API with Slack or Postgres (see comments inside).

Quick start

1. Define a workflow

Use generics on HITLWorkflow<InputType, OutputType> so ctx.input is typed in steps:

import { HITLWorkflow, approval, MemoryStore } from './hitl/index.js';

type In = { userId: string };
type Out = { status: string };

const store = new MemoryStore();

const wf = new HITLWorkflow<In, Out>({
  name: 'delete-user',
  store,
})
  .step('find-user', async (ctx) => ({
    user: { id: ctx.input.userId, name: 'Ada' },
  }))
  .step(
    'request-approval',
    approval({
      title: 'Delete user?',
      description: (c) =>
        `User ${(c.prevStep as { user: { name: string } }).user.name} will be deleted.`,
      requiredApprovers: 1,
    })
  )
  .step('execute', async (ctx) =>
    ctx.approval?.decision === 'reject'
      ? { status: 'cancelled' }
      : { status: 'deleted' }
  );

Orchestrator is exported as an alias for HITLWorkflow if you prefer that name.

2. Run and approve

import { startWorkflow, approveStep } from './hitl/index.js';

const run = await startWorkflow(wf, {
  input: { userId: 'user-123' },
  workflowId: 'delete-request-456',
});

console.log(run.status, run.currentStep); // paused at approval

await approveStep('delete-request-456', {
  stepId: 'request-approval',
  decision: 'approve',
  approvedBy: 'admin-1',
});

const result = await run.waitForCompletion();
console.log(result);

Same process: approveStep finds the active run in the same Node.js process that called startWorkflow. If the process restarts, load the same logical definition from code, then call await wf.resume(workflowId) to re-attach completion handling, then approve.

3. REST API (optional)

src/api/server.ts provides:

Method Path Purpose
GET /health Liveness
GET /api/pending Pending approvals from the configured store
POST /api/approve Body: { workflowId, stepId, decision, userId, comment? }

With DATABASE_URL set, the server uses PostgresStore; otherwise MemoryStore.

npm run dev:api
# http://127.0.0.1:3000

4. CLI

npm run cli

Example: start user-123, then approve <workflowId> request-approval approve OK (the bundled CLI uses the request-approval step from examples/delete-user.ts).

5. Demo entrypoint

npm run dev

Runs src/index.ts (sample delete-user workflow, pauses at approval).

Scripts

Script Purpose
npm run build Compile src/dist/
npm run dev Watch src/index.ts
npm run dev:api Watch REST server
npm run cli Interactive CLI
npm test All Vitest tests
npm run test:integration Integration tests
npm run test:timeout Timeout behavior
npm run test:sqlite SQLite store persistence
npm run test:runtime getPendingApprovals / config helpers
npm run lint tsc --noEmit

prepublishOnly runs npm run build automatically before npm publish so dist/ is always fresh.

Development

From the repo root: npm test and npm run lint before pushing. Scripts dev, dev:api, and cli run TypeScript via tsx and do not require dist/. You need npm run build for npm start / npm run start:api, for anything that imports from dist/, and before publishing. The default main entry is the demo (src/index.tsdist/index.js); library code lives under src/hitl/ and is exposed as hitl-agent-ts/hitl after build.

Docker

docker compose up --build

Builds the API image (Dockerfile) and starts PostgreSQL. The API process must still share the same workflow definitions and active runs as your worker if you use approveStep in-process; see “Same process” above.

Project layout

hitl-agent-ts/
├── src/
│   ├── hitl/
│   │   ├── index.ts          # public API barrel
│   │   ├── HITLWorkflow.ts   # workflow builder + execution
│   │   ├── runtime.ts        # startWorkflow, approveStep, getPendingApprovals
│   │   ├── approval.ts
│   │   ├── config.ts
│   │   ├── types.ts
│   │   ├── store/            # memory, sqlite, postgres
│   │   ├── notifiers/
│   │   └── testing.ts        # TestOrchestrator
│   ├── examples/delete-user.ts
│   ├── api/server.ts
│   ├── cli/index.ts
│   └── index.ts
├── tests/
├── package.json
├── tsconfig.json
├── vitest.config.ts
├── docker-compose.yml
├── Dockerfile
├── .env.example
├── LICENSE
└── README.md

Library API

Export Role
HITLWorkflow / Orchestrator Build steps; approval({ ... }) for gates
startWorkflow(wf, { input, workflowId? }) Returns WorkflowRun (waitForCompletion(), currentStep, status)
wf.resume(workflowId) Re-attach after restart or to re-register waiters
approveStep(workflowId, { stepId, decision, approvedBy, comment? }) Submit human decision
getPendingApprovals(store?, filter?) With store: getPendingApprovals(store) or getPendingApprovals(store, { approvedBy }). With configureHitl(store): getPendingApprovals() or getPendingApprovals({ approvedBy }) or getPendingApprovals(undefined, { approvedBy })
configureHitl(store) Default store for getPendingApprovals() when no store is passed
resetConfiguredStore() Clears the default store (tests / dev only)
MemoryStore, SQLiteStore, PostgresStore Backends
TestOrchestrator In-memory tests (orch.store must match the workflow’s store)

License

MIT — see LICENSE.

About

HITL agent workflows in TypeScript: pause for human approval, multi-approver gates, timeouts, audit-friendly stores, Slack/webhook notify.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors