Cookbook · AI integration
A runnable agent that can read AND write the graph. Each UPGClient method becomes a typed tool; Claude picks which to call in a loop.
Recipe
import { query, tool } from '@anthropic-ai/claude-agent-sdk'
import { UPGClient } from '@unified-product-graph/sdk'
import { z } from 'zod'
const upg = new UPGClient({ file: './product.upg' })
const tools = [
tool('list_nodes', {
description: 'List nodes by type, optionally filtered by status.',
parameters: z.object({
type: z.string(),
status: z.string().optional(),
}),
handler: async ({ type, status }) => {
const { nodes, total } = await upg.nodes.list({ type, status })
return { count: total, nodes: nodes.map(n => ({ id: n.id, title: n.title })) }
},
}),
tool('create_node', {
description: 'Create a typed node. Pass parent_id to auto-link to a parent.',
parameters: z.object({
type: z.string(),
title: z.string(),
parent_id: z.string().optional(),
}),
handler: async (args) => {
const { node } = await upg.nodes.create(args)
return { id: node.id, type: node.type, title: node.title }
},
}),
tool('connect', {
description: 'Create an edge between two existing nodes (edge type inferred).',
parameters: z.object({
source_id: z.string(),
target_id: z.string(),
}),
handler: async ({ source_id, target_id }) => {
const edge = await upg.edges.connect(source_id, target_id)
return { edge_id: edge.id, edge_type: edge.type }
},
}),
tool('health', {
description: 'Return the graph health score (0-10) and a structural digest.',
parameters: z.object({}),
handler: async () => upg.health(),
}),
]
for await (const event of query({
prompt: `Find the persona "Busy Parent". Create a job
"Stay consistent over 30 days" under it. Then report graph health.`,
options: { tools, model: 'claude-sonnet-4-6' },
})) {
if (event.type === 'text_delta') process.stdout.write(event.text)
if (event.type === 'tool_use') console.error('\n→ tool:', event.name, event.input)
}
await upg.close()What it does
The Agent SDK loop is: Claude picks a tool, the handler runs the corresponding UPGClient call, the result feeds back into the next turn. Map every operation you want Claude to perform to a tool and keep schemas tight (zod) so Claude does not have to guess. Errors thrown from the handler become tool errors Claude can recover from.
Variations
Read-only agent (no create/connect)
// Just include list_nodes, search, traverse_edges, health.
// Useful for an analyst-style agent that explains the graph but cannot mutate it.With user-approval gate on writes
tool('create_node', { /* ... */, handler: async (args) => {
const ok = await askUser(`Create ${args.type} "${args.title}"?`)
if (!ok) throw new Error('user rejected')
return upg.nodes.create(args)
}})See also