@mondaydotcomorg/atp-langchain
LangChain and LangGraph integration for Agent Tool Protocol with production-ready human-in-the-loop support.
Overview
This package integrates ATP with LangChain and LangGraph, enabling agents to generate and execute ATP code as tools. Includes production-ready async approval workflows using LangGraph interrupts and checkpoints.
Installation
npm install @mondaydotcomorg/atp-langchain @langchain/core @langchain/langgraph
Architecture
graph TB
LangChain[LangChain Agent] --> Tools[ATP Tools]
Tools --> Client[LangGraphATPClient]
Client --> Server[ATP Server]
Server --> Runtime[Runtime APIs]
Runtime --> LLM[atp.llm.*]
Runtime --> Embed[atp.embedding.*]
Runtime --> Approval[atp.approval.*]
LLM --> Callback[Client LLM Callback]
Embed --> EmbedCallback[Client Embedding Callback]
Approval --> Interrupt[LangGraph Interrupt]
Interrupt --> Checkpoint[Save State]
Checkpoint --> Resume[Resume Later]
Features
- 🤖 LangChain Tools - Use ATP as tools in any LangChain agent
- 🔄 LangGraph Interrupts - Production-ready async approvals
- 🧠 LLM Sampling -
atp.llm.call() routes to your LangChain LLM
- 🔍 Embedding Support -
atp.embedding.* routes to your embeddings model
- ✅ Approval Workflows - Human-in-the-loop via LangGraph checkpoints
- 💾 State Persistence - PostgreSQL/Redis checkpointing
- 📝 TypeScript - Full type safety
Quick Start
Simple Agent
import { ChatOpenAI } from '@langchain/openai';
import { createReactAgent } from '@langchain/langgraph/prebuilt';
import { createATPTools } from '@mondaydotcomorg/atp-langchain';
const llm = new ChatOpenAI({ modelName: 'gpt-4' });
const { tools } = await createATPTools('http://localhost:3333', 'your-api-key', { llm });
const agent = createReactAgent({ llm, tools });
const result = await agent.invoke({
messages: [{ role: 'user', content: 'Execute ATP code to get a joke from the LLM' }],
});
Production Agent with Approvals
import { MemorySaver } from '@langchain/langgraph';
import { createATPTools, ApprovalRequiredException } from '@mondaydotcomorg/atp-langchain';
const llm = new ChatOpenAI({ modelName: 'gpt-4' });
const { client, tools, isApprovalRequired, resumeWithApproval } =
await createATPTools('http://localhost:3333', 'api-key', { llm });
const checkpointer = new MemorySaver();
const agent = createReactAgent({ llm, tools, checkpointSaver: checkpointer });
try {
await agent.invoke({ messages: [...] }, { configurable: { thread_id: 'thread-1' } });
} catch (error) {
if (isApprovalRequired(error)) {
const { executionId, message, context } = error.approvalRequest;
console.log(`Approval needed: ${message}`);
await notifyUser(message);
const approved = await waitForUserApproval(executionId);
const result = await resumeWithApproval(executionId, approved);
}
}
With Embeddings
import { ChatOpenAI, OpenAIEmbeddings } from '@langchain/openai';
import { createATPTools } from '@mondaydotcomorg/atp-langchain';
const llm = new ChatOpenAI({ modelName: 'gpt-4' });
const embeddings = new OpenAIEmbeddings({ model: 'text-embedding-3-small' });
const { tools } = await createATPTools('http://localhost:3333', 'api-key', {
llm,
embeddings,
});
const agent = createReactAgent({ llm, tools });
How It Works
ATP Runtime in LangChain
When agents use ATP tools, they can generate TypeScript code using ATP's runtime APIs:
const idea = await atp.llm.call({
prompt: 'Generate a product idea',
});
const embeddingId = await atp.embedding.embed(idea);
const approval = await atp.approval.request(`Launch product: ${idea}?`, { idea });
if (approval.approved) {
return await atp.llm.call({
prompt: `Create marketing copy for: ${idea}`,
});
}
LLM Sampling
atp.llm.call() routes to your LangChain LLM:
- Uses the same LLM as your agent
- Fresh context for sub-reasoning
- Supports
call(), extract(), classify()
const analysis = await atp.llm.call({
prompt: 'Analyze this data: ' + JSON.stringify(data),
temperature: 0.7,
model: 'gpt-4',
});
Approval Interrupts
When ATP code calls atp.approval.request(), LangGraph interrupts:
- Pause - Execution pauses, state saved to checkpoint
- Notify - Your code sends approval request (Slack, email, UI)
- Wait - User reviews asynchronously (hours/days OK)
- Resume - Call
resumeWithApproval() with decision
- Continue - Execution resumes from checkpoint
sequenceDiagram
participant Agent
participant ATP
participant Checkpoint
participant User
Agent->>ATP: Execute code
ATP->>ATP: atp.approval.request()
ATP->>Checkpoint: Save state
ATP-->>Agent: Throw ApprovalRequiredException
Agent->>User: Notify (Slack/Email)
Note over User: User reviews<br/>(async, can take hours)
User->>Agent: Approve/Deny
Agent->>ATP: resumeWithApproval(executionId, approved)
ATP->>Checkpoint: Restore state
ATP->>ATP: Continue execution
ATP-->>Agent: Final result
API Reference
createATPTools()
async function createATPTools(
serverUrl: string,
apiKey: string,
options: CreateATPToolsOptions
): Promise<ATPToolsResult>;
Options:
interface CreateATPToolsOptions {
llm: BaseChatModel;
embeddings?: Embeddings;
useLangGraphInterrupts?: boolean;
approvalHandler?: ApprovalHandler;
defaultExecutionConfig?: ExecutionConfig;
}
Returns:
interface ATPToolsResult {
client: LangGraphATPClient;
tools: Tool[];
isApprovalRequired: (error: any) => boolean;
resumeWithApproval: (executionId: string, approved: boolean, reason?: string) => Promise<any>;
}
LangGraphATPClient
class LangGraphATPClient extends AgentToolProtocolClient {
execute(code: string, config?: ExecutionConfig): Promise<ExecutionResult>;
resumeWithApproval(executionId: string, approved: boolean, reason?: string): Promise<any>;
getPendingApproval(executionId: string): Promise<ApprovalRequest | null>;
}
ApprovalRequiredException
class ApprovalRequiredException extends Error {
approvalRequest: {
executionId: string;
message: string;
context?: any;
timestamp: number;
};
}
Production Patterns
PostgreSQL Checkpointing
import { PostgresSaver } from '@langchain/langgraph-checkpoint-postgres';
const checkpointer = new PostgresSaver({
connectionString: process.env.DATABASE_URL,
});
const agent = createReactAgent({
llm,
tools,
checkpointSaver: checkpointer,
});
Async Approval via Slack
if (isApprovalRequired(error)) {
const { executionId, message, context } = error.approvalRequest;
await db.approvals.create({
id: executionId,
message,
context,
status: 'pending',
});
await slack.chat.postMessage({
channel: '#approvals',
text: message,
blocks: [
{
type: 'actions',
elements: [
{ type: 'button', text: 'Approve', action_id: 'approve' },
{ type: 'button', text: 'Deny', action_id: 'deny' },
],
},
],
});
}
app.post('/slack/actions', async (req, res) => {
const { action_id } = req.body;
const executionId = req.body.state.executionId;
const approved = action_id === 'approve';
const result = await resumeWithApproval(executionId, approved);
await db.approvals.update(executionId, { status: approved ? 'approved' : 'denied' });
res.json({ ok: true });
});
Multiple Sequential Approvals
const step1 = await atp.approval.request('Approve step 1?');
if (!step1.approved) return { cancelled: true };
const step2 = await atp.approval.request('Approve step 2?');
if (!step2.approved) return { cancelled: true };
return { success: true };
Each atp.approval.request() triggers a new interrupt → checkpoint → resume cycle.
Comparison: Direct vs Interrupt Mode
Direct Mode
const { tools } = await createATPTools(url, key, {
llm,
useLangGraphInterrupts: false,
approvalHandler: async (message) => {
return await promptUser(message);
},
});
Pros: Simpler setup, good for CLI tools
Cons: Blocks execution, no persistence, not production-ready
Interrupt Mode ⭐ Recommended
const { tools, isApprovalRequired, resumeWithApproval } = await createATPTools(url, key, { llm });
try {
await agent.invoke({ messages });
} catch (error) {
if (isApprovalRequired(error)) {
await handleApprovalAsync(error.approvalRequest);
}
}
Pros: Non-blocking, state persists, production-ready, multi-user support
Cons: Slightly more complex setup
Examples
See examples/langchain-react-agent/:
simple-test.ts - Integration test with all 3 ATP tools
simple-agent.ts - Basic React agent without approvals
agent.ts - Production agent with interrupts and checkpoints
RAG with Embeddings
import { ChatOpenAI, OpenAIEmbeddings } from '@langchain/openai';
import { createATPTools } from '@mondaydotcomorg/atp-langchain';
const llm = new ChatOpenAI({ modelName: 'gpt-4' });
const embeddings = new OpenAIEmbeddings();
const { tools } = await createATPTools(url, key, { llm, embeddings });
const agent = createReactAgent({ llm, tools });
const result = await agent.invoke({
messages: [
{
role: 'user',
content: `Use ATP to:
1. Embed these documents: ["AI is...", "ML is...", "DL is..."]
2. Search for content similar to "neural networks"
3. Use atp.llm.call() to answer based on results`,
},
],
});
TypeScript Support
import type {
LangGraphATPClient,
LangGraphATPClientOptions,
ApprovalRequest,
ApprovalResponse,
CreateATPToolsOptions,
ATPToolsResult,
} from '@mondaydotcomorg/atp-langchain';
Requirements
- Node.js 18+
- TypeScript 5.0+
@langchain/core ^0.3.0
@langchain/langgraph ^0.2.0
License
MIT
Learn More