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

  1. Keep it simple - One responsibility per action
  2. Validate early - Check inputs before processing
  3. Log errors - Use console.error/print for debugging
  4. Return consistently - Always match your schemas
  5. Handle edge cases - Empty results, missing data, timeouts

Next: Learn about Testing Locally.