Building the Graph
The graph is your Trik’s implementation. It receives actions and returns responses.
Basic Structure
interface TrikInput {
action: string;
input: Record<string, unknown>;
session?: {
sessionId: string;
history: SessionHistoryEntry[];
};
config?: TrikConfigContext;
storage?: TrikStorageContext;
}
interface TrikOutput {
responseMode: 'template' | 'passthrough';
agentData?: Record<string, unknown>;
userContent?: {
contentType: string;
content: string;
metadata?: Record<string, unknown>;
};
}
export const graph = {
async invoke(input: TrikInput): Promise<TrikOutput> {
// Your implementation
}
}Template Mode Response
Return structured data that the agent can reason about:
export const graph = {
async invoke({ action, input }) {
if (action === 'search') {
const results = await searchDatabase(input.query)
if (results.length === 0) {
return {
responseMode: 'template',
agentData: {
template: 'empty',
query: input.query
}
}
}
return {
responseMode: 'template',
agentData: {
template: 'success',
count: results.length,
resultIds: results.map(r => r.id)
}
}
}
}
}The template field tells the gateway which response template to use.
Passthrough Mode Response
Return content that goes directly to the user:
export const graph = {
async invoke({ action, input }) {
if (action === 'getArticle') {
const article = await fetchArticle(input.articleId)
if (!article) {
// Fall back to template for errors
return {
responseMode: 'template',
agentData: { template: 'not_found' }
}
}
return {
responseMode: 'passthrough',
userContent: {
contentType: 'article',
content: `# ${article.title}\n\n${article.body}`,
metadata: {
title: article.title,
wordCount: article.body.split(' ').length
}
}
}
}
}
}Handling Multiple Actions
export const graph = {
async invoke({ action, input, session }) {
switch (action) {
case 'search':
return handleSearch(input)
case 'details':
return handleDetails(input, session)
case 'list':
return handleList(input, session)
default:
return {
responseMode: 'template',
agentData: { template: 'error' }
}
}
}
}
function handleSearch(input: { topic: string }) {
const results = searchArticles(input.topic)
return {
responseMode: 'template',
agentData: {
template: results.length > 0 ? 'success' : 'empty',
count: results.length,
articleIds: results.map(r => r.id)
}
}
}Using Session History
Access previous interactions for context:
export const graph = {
async invoke({ action, input, session }) {
const history = session?.history ?? []
if (action === 'details' && input.reference) {
// Resolve "the second one" using session history
const articleId = resolveReference(input.reference, history)
return handleDetails({ articleId }, session)
}
// ...
}
}
function resolveReference(reference: string, history: SessionHistoryEntry[]): string | null {
// Find the last search in history
const lastSearch = history
.slice()
.reverse()
.find(entry => entry.action === 'search')
if (!lastSearch?.agentData) return null
const { articleIds } = lastSearch.agentData as { articleIds: string[] }
// Handle ordinal references: "the first one", "the second one"
const ordinals = ['first', 'second', 'third', 'fourth', 'fifth']
const ordinalIndex = ordinals.findIndex(o => reference.toLowerCase().includes(o))
if (ordinalIndex >= 0 && articleIds[ordinalIndex]) {
return articleIds[ordinalIndex]
}
return null
}Calling External APIs
import Anthropic from '@anthropic-ai/sdk'
const anthropic = new Anthropic()
export const graph = {
async invoke({ action, input, config }) {
if (action === 'summarize') {
const article = await fetchArticle(input.articleId)
const response = await anthropic.messages.create({
model: 'claude-sonnet-4-20250514',
max_tokens: 200,
messages: [{
role: 'user',
content: `Summarize in 2 sentences: ${article.content}`
}]
})
const summary = response.content[0].type === 'text'
? response.content[0].text
: ''
return {
responseMode: 'passthrough',
userContent: {
contentType: 'summary',
content: summary,
metadata: { articleId: input.articleId }
}
}
}
}
}Error Handling
Always return valid responses, even on error:
export const graph = {
async invoke({ action, input }) {
try {
const result = await riskyOperation(input)
return {
responseMode: 'template',
agentData: { template: 'success', ...result }
}
} catch (error) {
console.error('Trik error:', error)
return {
responseMode: 'template',
agentData: {
template: 'error',
// Don't expose error details to agent
}
}
}
}
}Best Practices
- Keep it simple - One responsibility per action
- Validate early - Check inputs before processing
- Log errors - Use console.error/print for debugging
- Return consistently - Always match your schemas
- Handle edge cases - Empty results, missing data, timeouts
Next: Learn about Testing Locally.