Human-in-the-Loop
medium15 minhuman-in-loop.ts
Nothing gets published until an editor approves the draft the agent wrote.
What it shows
- Add an approval step with
humanWorkflowNode(approvalNode(...)) - Answer it from outside the run through the
ApprovalStore— like an API handler or UI would - Branch on the decision
The code
human-in-loop.ts
import { Agent, Cogitator } from '@cogitator-ai/core';
import {
InMemoryApprovalStore,
WorkflowBuilder,
WorkflowExecutor,
agentNode,
approvalNode,
humanWorkflowNode,
} from '@cogitator-ai/workflows';
const apiKey = process.env.GOOGLE_API_KEY;
if (!apiKey) throw new Error('Set GOOGLE_API_KEY');
interface PublishingState {
[key: string]: unknown;
topic: string;
draft: string;
approved: boolean;
status: string;
}
const cog = new Cogitator({ llm: { providers: { google: { apiKey } } } });
const approvalStore = new InMemoryApprovalStore();
const writer = new Agent({
name: 'content-writer',
model: 'google/gemini-3.5-flash-lite',
instructions: 'You are a technical writer. Write a blog post under 150 words with a title.',
temperature: 0.7,
});
const write = agentNode<PublishingState>(writer, {
inputMapper: (state) => `Write a short blog post about: ${state.topic}`,
stateMapper: (result) => ({ draft: result.output }),
});
const review = humanWorkflowNode(
approvalNode<PublishingState>('editorial-review', {
title: 'Editorial review',
description: (state) => `Publish this draft?\n\n${state.draft}`,
assignee: 'editor@company.com',
timeout: 60 * 60 * 1000,
timeoutAction: 'reject',
}),
{ stateMapper: (result) => ({ approved: result.approved }) }
);
const workflow = new WorkflowBuilder<PublishingState>('content-publishing')
.initialState({ topic: '', draft: '', approved: false, status: 'draft' })
.addNode(write.name, write.fn)
.addNode(review.name, review.fn, { after: [write.name] })
.addConditional('decide', (state) => (state.approved ? 'publish' : 'archive'), {
after: [review.name],
})
.addNode('publish', async () => ({ state: { status: 'published' } }), { after: ['decide'] })
.addNode('archive', async () => ({ state: { status: 'rejected' } }), { after: ['decide'] })
.entryPoint(write.name)
.build();
const run = new WorkflowExecutor(cog).execute(
workflow,
{ topic: 'Building type-safe AI agents with TypeScript' },
{ approvalStore }
);
async function nextPendingRequest() {
for (;;) {
const [request] = await approvalStore.getPendingRequests();
if (request) return request;
await new Promise((resolve) => setTimeout(resolve, 100));
}
}
const request = await nextPendingRequest();
console.log(`${request.title} for ${request.assignee}:\n${request.description}`);
await approvalStore.submitResponse({
requestId: request.id,
decision: true,
respondedBy: 'editor@company.com',
respondedAt: Date.now(),
comment: 'Ship it',
});
const result = await run;
console.log('Status:', result.state.status);
approvalStore.dispose();
await cog.close();Run it
install · bash
pnpm add @cogitator-ai/core @cogitator-ai/workflowsEnvironment
GOOGLE_API_KEYrun · bash
GOOGLE_API_KEY=your-key npx tsx human-in-loop.tsOr run the full example from a clone of the repository (keys in .env):
repository · bash
npx tsx examples/workflows/02-human-in-loop.ts