# Distri - AI Assistant Reference # Generated: 2026-02-27 # Live docs: https://distri.dev/docs # Homepage: https://distri.dev Distri is a composable AI agent runtime for building in-product deep agents. Define agents in markdown, connect your product functions as tools, deploy anywhere, CLI, API server, or embedded React UI. Packages: - @distri/core: TypeScript client + tool types - @distri/react: React components and hooks (DistriProvider, Chat, useAgent) - distri (crate): Rust client - distri CLI: Command-line interface Cloud API: https://api.distri.dev Dashboard: https://app.distri.dev ================================================================================ TABLE OF CONTENTS ================================================================================ 1. AUTHENTICATION & TOKEN GENERATION 2. REACT SETUP (DistriProvider) 3. AGENT DEFINITION FORMAT 4. CLIENT-SIDE TOOLS (DistriFnTool) 5. SESSION STORE & TRANSIENT PARTS 6. LIFECYCLE HOOKS & DYNAMIC CONTEXT 7. LIFECYCLE EVENTS 8. API REFERENCE 9. CLI REFERENCE 10. COMPLETE EXAMPLES ================================================================================ ## 1. AUTHENTICATION & TOKEN GENERATION ================================================================================ ### Prerequisites 1. Create an account at https://app.distri.dev 2. Create a workspace (Settings → Workspaces), note the Workspace ID 3. Generate an API key (Settings → API Keys), starts with `dak_`, shown once ### Token Generation API Your backend exchanges the API key for short-lived tokens. The frontend never sees the API key. ENDPOINT: POST https://api.distri.dev/v1/token HEADERS: Authorization: Bearer X-Workspace-Id: Content-Type: application/json BODY (optional): { "identifier_id": "user-123", // Per-user usage tracking "limits": { // Per-user limits (optional) "daily_tokens": 10000, "monthly_tokens": 100000 } } RESPONSE: { "access_token": "...", // Short-lived, 30 minutes "refresh_token": "...", // Long-lived, 90 days "expires_at": "2026-02-27T12:30:00Z" } ### Backend Token Endpoint (Node.js/Express) ```typescript import express from 'express'; const app = express(); app.post('/distri/token', async (req, res) => { const response = await fetch('https://api.distri.dev/v1/token', { method: 'POST', headers: { 'Authorization': `Bearer ${process.env.DISTRI_API_KEY}`, 'X-Workspace-Id': process.env.DISTRI_WORKSPACE_ID, 'Content-Type': 'application/json', }, body: JSON.stringify({ identifier_id: req.user?.id, // Optional: per-user tracking }), }); if (!response.ok) { return res.status(500).json({ error: 'Token generation failed' }); } const { access_token, refresh_token, expires_at } = await response.json(); res.json({ access_token, refresh_token, expires_at }); }); ``` ### Generic Fetch Pattern ```typescript async function getDistriTokens(userId?: string) { const response = await fetch('https://api.distri.dev/v1/token', { method: 'POST', headers: { 'Authorization': `Bearer ${process.env.DISTRI_API_KEY}`, 'X-Workspace-Id': process.env.DISTRI_WORKSPACE_ID, 'Content-Type': 'application/json', }, body: userId ? JSON.stringify({ identifier_id: userId }) : undefined, }); return response.json(); // Returns: { access_token, refresh_token, expires_at } } ``` ### Alternative: Public Embed (No Backend) For prototypes, use a public clientId instead of tokens. Rate-limited and requires Cloudflare Turnstile. Create at Settings → Public Clients. ```tsx ``` ### API Keys vs Client IDs | Feature | API Key (backend) | Client ID (frontend) | |---------------|-------------------------|-----------------------------| | Use case | Server-to-server | Browser/public embed | | Access level | Full account access | Read-only + invoke | | Security | Keep secret | Can be public | | Auth header | Authorization: Bearer | Passed via clientId config | ================================================================================ ## 2. REACT SETUP (DistriProvider) ================================================================================ Install: ```bash npm install @distri/core @distri/react ``` ### DistriProvider with Token Auth (Production) ```tsx import { useEffect, useState } from 'react'; import { DistriProvider, Chat, useAgent } from '@distri/react'; function App() { const [tokens, setTokens] = useState<{ access_token: string; refresh_token: string; } | null>(null); useEffect(() => { fetch('/distri/token', { method: 'POST' }) .then((res) => res.json()) .then(setTokens); }, []); if (!tokens) return
Loading...
; return ( { const res = await fetch('/distri/token', { method: 'POST' }); const { access_token } = await res.json(); return access_token; }, }} > ); } function ChatPage() { const { agent, loading, error } = useAgent({ agentIdOrDef: 'your-agent-id', }); const [threadId] = useState(() => crypto.randomUUID()); if (loading) return
Loading agent...
; if (error) return
Error: {error.message}
; if (!agent) return null; return ; } ``` ### DistriProvider with Self-Hosted Server ```tsx ``` ### DistriClientConfig Interface ```typescript interface DistriClientConfig { baseUrl: string; // Distri API URL accessToken?: string; // Short-lived access token refreshToken?: string; // Long-lived refresh token workspaceId?: string; // Workspace UUID clientId?: string; // Public client ID (for embeds) authReady?: boolean; // Gate rendering until auth ready onTokenRefresh?: () => Promise; // Refresh callback headers?: Record; // Custom headers debug?: boolean; // Enable debug logging } ``` ### Chat Component Props | Prop | Type | Description | |---------------------|-----------------------|------------------------------------| | threadId | string | Unique conversation thread ID | | agent | Agent | Agent instance from useAgent() | | externalTools | DistriAnyTool[] | Client-side tools agent can call | | enableHistory | boolean | Load previous messages | | maxWidth | string | CSS max-width for container | | getMetadata | () => Promise | Inject dynamic context per message | | onChatInstanceReady | (instance) => void | Callback with chat instance ref | ### Available React Hooks | Hook | Description | |-----------------------|------------------------------------------------| | useAgent() | Load an agent by ID or definition | | useChat() | Full chat state (messages, streaming, tools) | | useChatMessages() | Access chat message history | | useDistri() | Access the Distri client | | useDistriToken() | Get current auth token state | | useWorkspace() | Get and set workspace context | | useAgentDefinitions() | List all available agents | | useThreads() | List and manage conversation threads | | useAgentEvents() | Listen to agent lifecycle events | ================================================================================ ## 3. AGENT DEFINITION FORMAT ================================================================================ Agents are markdown files with TOML frontmatter between `---` markers. ### Minimal Agent ```md --- name = "my_agent" description = "A helpful assistant" [tools] external = ["*"] [model_settings] model = "gpt-4.1-mini" --- # ROLE You are a helpful assistant. # TASK {{task}} {{#if available_tools}} # TOOLS {{{available_tools}}} {{/if}} ``` ### Core Properties | Property | Type | Default | Description | |----------------|--------|--------------|--------------------------------------| | name | string | required | Unique identifier (valid JS id) | | description | string | required | Brief purpose description | | version | string | "0.2.2" | Agent definition version | | max_iterations | number | None | Maximum execution iterations | | history_size | number | 5 | Previous messages in context | | icon_url | string | None | Agent icon URL for A2A discovery | ### Model Settings ```toml [model_settings] model = "gpt-4.1-mini" # Model identifier temperature = 0.7 # 0.0-2.0, lower = more deterministic max_tokens = 1000 # Max tokens in response context_size = 20000 # Max context window [model_settings.provider] name = "openai" # openai, openai_compat, vllora # For custom endpoints: # name = "openai_compat" # base_url = "https://your-endpoint.com/v1" ``` ### Tools Configuration ```toml [tools] builtin = ["final", "transfer_to_agent"] # Built-in tools external = ["*"] # Client-side tools ("*" = all) [[tools.mcp]] server = "fetch" include = ["*"] exclude = ["delete_*"] [tools.packages] search = ["search", "search_images"] # DAP package tools ``` ### Sub-Agents ```toml sub_agents = ["research_agent", "writing_agent"] ``` Combined with `transfer_to_agent` builtin tool, enables agent handover. ### Skills (A2A Discovery) ```toml [[skills]] id = "web_search" name = "Web Search" description = "Search the web for information" tags = ["search", "research"] examples = ["Find the latest news about AI regulations"] ``` Skills are exposed via: GET /agents/{name}/.well-known/agent.json ### Browser Configuration ```toml [browser_config] enabled = true headless = true persist_session = false ``` ### Secrets ```toml [secrets] required = ["STRIPE_API_KEY", "SENDGRID_KEY"] ``` Secrets are managed in the dashboard (Settings → Secrets) or via CLI: ```bash distri secrets set STRIPE_API_KEY "sk_live_..." distri secrets list distri secrets delete STRIPE_API_KEY ``` ### Handlebars Templates Agent instructions support Handlebars for dynamic content: | Placeholder | Description | |-----------------------|-------------------------------------------------| | {{task}} | The user's input message | | {{{available_tools}}} | Auto-generated tool descriptions (triple braces)| | {{> reasoning}} | Include the reasoning partial | | {{#if cond}}...{{/if}}| Conditional sections | Values from getMetadata's dynamic_sections also become template variables. ### Feature Flags ```toml enable_reflection = false # Reflection subagent enable_todos = false # TODO management write_large_tool_responses_to_fs = false # Write large outputs as artifacts ``` ================================================================================ ## 4. CLIENT-SIDE TOOLS (DistriFnTool) ================================================================================ Client-side tools let agents call your product functions. They execute in the browser and are passed to the Chat component via externalTools. ### DistriFnTool Interface ```typescript interface DistriFnTool { type: 'function'; name: string; description: string; parameters: { type: 'object'; properties: Record; required?: string[]; }; handler: (input: any) => Promise< string | number | boolean | null | object | DistriPart[] >; is_final?: boolean; // Terminates agent after this tool autoExecute?: boolean; // Runs without user confirmation } ``` ### DistriUiTool Interface For tools that render custom React UI: ```typescript interface DistriUiTool { type: 'ui'; name: string; description: string; parameters: object; component: (props: { toolCall: ToolCall; completeTool: (result: ToolResult) => void; }) => React.ReactNode; } ``` ### Basic Tool Definition ```typescript import type { DistriFnTool } from '@distri/core'; const searchTool: DistriFnTool = { name: 'search_products', type: 'function', description: 'Search products by name or category', parameters: { type: 'object', properties: { query: { type: 'string', description: 'Search query' }, category: { type: 'string', description: 'Product category' }, limit: { type: 'number', description: 'Max results', default: 10 }, }, required: ['query'], }, handler: async (input) => { const res = await fetch(`/api/products/search`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(input), }); return res.json(); }, }; ``` ### Handler Return Types | Return Type | Usage | |--------------|----------------------------------------------------| | string | Simple text: `return 'Done!'` | | number | Numeric: `return 42` | | boolean | True/false: `return true` | | null | Empty response: `return null` | | object | Structured: `return { status: 'ok', count: 5 }` | | DistriPart[] | Rich multi-part (see Transient Parts below) | ### Tool Factory Pattern Create tools that capture application state via closures: ```typescript import type { DistriFnTool } from '@distri/core'; export function createProductTools(apiBase: string, authToken: string): DistriFnTool[] { const headers = { 'Authorization': `Bearer ${authToken}`, 'Content-Type': 'application/json', }; return [ { name: 'search_products', type: 'function', description: 'Search products by name or category', parameters: { type: 'object', properties: { query: { type: 'string', description: 'Search query' }, category: { type: 'string', description: 'Product category' }, }, required: ['query'], }, handler: async (input) => { const res = await fetch(`${apiBase}/products/search`, { method: 'POST', headers, body: JSON.stringify(input), }); return res.json(); }, }, { name: 'update_product', type: 'function', description: 'Update a product field', parameters: { type: 'object', properties: { product_id: { type: 'string' }, field: { type: 'string' }, value: { type: 'string' }, }, required: ['product_id', 'field', 'value'], }, handler: async (input) => { const res = await fetch(`${apiBase}/products/${input.product_id}`, { method: 'PATCH', headers, body: JSON.stringify({ [input.field]: input.value }), }); return res.ok ? 'Updated successfully' : 'Update failed'; }, }, ]; } ``` ### Passing Tools to Chat ```tsx import { Chat, useAgent } from '@distri/react'; import type { DistriAnyTool } from '@distri/react'; function MyChat() { const { agent } = useAgent({ agentIdOrDef: 'my_agent' }); const [threadId] = useState(() => crypto.randomUUID()); const [tools, setTools] = useState([]); useEffect(() => { setTools(createProductTools('/api', authToken)); }, [authToken]); if (!agent) return null; return ( ); } ``` ### Agent Must Allow External Tools In the agent definition TOML: ```toml [tools] external = ["*"] # Allow all client-side tools # Or whitelist specific tools: # external = ["search_products", "update_product"] ``` ### Best Practices 1. Clear descriptions, help the agent understand WHEN to use the tool: description: 'Search products in the catalog. Use when the user asks about product availability, pricing, or finding items.' 2. Minimal responses, return concise data to avoid context bloat 3. Error handling, return structured errors, not thrown exceptions: handler: async (input) => { try { ... } catch (e) { return { success: false, error: e.message }; } } 4. is_final: true, use on tools that end the conversation (e.g., create_ticket) ================================================================================ ## 5. SESSION STORE & TRANSIENT PARTS ================================================================================ ### Session Store Thread-scoped key-value storage for state across agent iterations. ```typescript import { Distri } from '@distri/core'; const client = new Distri({ baseUrl: 'http://localhost:8787/api/v1' }); const threadId = 'thread-123'; // Set / Get / Delete await client.setSessionValue(threadId, 'user_pref', { theme: 'dark' }); const val = await client.getSessionValue(threadId, 'user_pref'); const all = await client.getSessionValues(threadId); await client.deleteSessionValue(threadId, 'user_pref'); await client.clearSession(threadId); ``` ### User Parts Prefixed with `__user_part_`, automatically attach to user messages: ```typescript await client.setUserPartText(threadId, 'observation', 'User clicked submit'); await client.setUserPartImage(threadId, 'screenshot', { bytes: base64String, mimeType: 'image/png', name: 'viewport.png', }); await client.deleteUserPart(threadId, 'observation'); await client.clearUserParts(threadId); ``` ### Session Value Expiry ```typescript const expiry = new Date(); expiry.setHours(expiry.getHours() + 24); await client.setSessionValue(threadId, 'temp', { data: 'val' }, expiry.toISOString()); ``` ### Transient Parts (__metadata: { save: false }) Include data in agent context WITHOUT persisting to conversation history. Critical for browser automation, screenshots, and large ephemeral data. ```typescript import type { DistriPart } from '@distri/core'; handler: async (input): Promise => { const screenshot = await captureScreenshot(); return [ { part_type: 'text', data: `=== BROWSER STATE ===\nURL: ${pageState.url}`, __metadata: { save: false }, // NOT saved to thread history }, { part_type: 'image', data: screenshot, __metadata: { save: false }, // NOT saved to thread history }, ]; } ``` When to use __metadata: { save: false }: - Browser screenshots (large, only relevant for current step) - DOM snapshots (change every step) - Intermediate observations (needed now, not in history) - Verbose tool output (would clutter the thread) Without save: false, every tool response is persisted. For multi-step agents (30+ steps), this quickly fills the context window. ### REST API Endpoints Set: POST /api/v1/threads/{thread_id}/session Body: { "key": "name", "value": {...}, "expires_at": "..." } Get: GET /api/v1/threads/{thread_id}/session/{key} All: GET /api/v1/threads/{thread_id}/session Delete: DELETE /api/v1/threads/{thread_id}/session/{key} Clear: DELETE /api/v1/threads/{thread_id}/session ================================================================================ ## 6. LIFECYCLE HOOKS & DYNAMIC CONTEXT ================================================================================ ### beforeExecute Hook Fires before each agent execution step. Returns dynamic_values that become Handlebars template variables in the agent prompt. ```typescript import type { HookHandler } from '@distri/core'; const beforeExecute: HookHandler = async (req) => { // req.context has agent_id, thread_id, task_id, run_id const appState = await getApplicationState(); return { dynamic_values: { current_user: appState.userName, active_items: appState.items.length, }, }; }; ``` In the agent definition, these become template variables: ```md # CONTEXT User: {{current_user}} Active items: {{active_items}} ``` ### Configuring Hooks ```toml name = "my_agent" hooks = ["my_hook"] ``` ### Dynamic Context with getMetadata The getMetadata callback on Chat injects fresh context before each message: ```tsx ({ dynamic_sections: { lesson_context: `Page ${currentPage} of lesson ${lessonId}`, student_answers: JSON.stringify(answers), }, })} /> ``` Values in dynamic_sections become Handlebars variables in the agent prompt: ```md # LESSON CONTEXT {{lesson_context}} # STUDENT ANSWERS {{student_answers}} ``` ### Hook Kinds | Hook | When It Fires | Use Case | |---------------|----------------------------|-----------------------------| | beforeExecute | Before each execution step | Inject context, modify parts| | onPlanStart | Agent begins planning | Log, inject planning context| | onPlanEnd | Planning completes | Review plan before execution| | onStepEnd | After each step | Log progress, update UI | ================================================================================ ## 7. LIFECYCLE EVENTS ================================================================================ Distri emits structured events for every agent run via SSE streaming. ### Run Events | Event | Key Payload Fields | |---------------|--------------------------------------------| | run_started | thread_id, run_id, agent_id | | plan_started | initial_plan | | plan_finished | total_steps | | run_finished | success, total_steps, failed_steps | | run_error | message, code? | ### Step & Tool Events | Event | Key Payload Fields | |----------------------|-------------------------------------------| | step_started | step_id, step_index | | step_completed | step_id, success | | tool_execution_start | tool_call_id, tool_call_name, input | | tool_execution_end | tool_call_id, tool_call_name, success | | tool_calls | step_id, tool_calls[] | | tool_results | step_id, results[] | ### Streaming Message Events | Event | Key Payload Fields | |----------------------|-------------------------------------------| | text_message_start | message_id, step_id, role, is_final? | | text_message_content | message_id, delta, stripped_content? | | text_message_end | message_id, step_id | ### Agent Handover | Event | Key Payload Fields | |----------------|--------------------------------------------| | agent_handover | from_agent, to_agent, reason? | ### Listening in React ```tsx import { useAgentEvents } from '@distri/react'; useAgentEvents({ onRunStarted: (e) => console.log('Run:', e.run_id), onToolExecutionStart: (e) => console.log('Tool:', e.tool_call_name), onTextMessageContent: (e) => console.log('Stream:', e.delta), onRunFinished: (e) => console.log('Done:', e.success), }); ``` ================================================================================ ## 8. API REFERENCE ================================================================================ ### Authentication All requests require: Authorization: Bearer X-Workspace-Id: ### Token Endpoints Issue Token: POST /v1/token Body: { "identifier_id": "user-123", "limits": { "daily_tokens": 10000 } } Response: { "access_token", "refresh_token", "expires_at" } ### Agent Endpoints List: GET /agents Get: GET /agents/{id} Card: GET /agents/{id}/.well-known/agent.json Invoke: POST /v1/agents/{id} Body (JSON-RPC): { "jsonrpc": "2.0", "id": "1", "method": "message/send", "params": { "message": { "kind": "message", "role": "user", "parts": [{ "kind": "text", "text": "Hello!" }] } } } ### Session Endpoints Set: POST /api/v1/threads/{thread_id}/session Get: GET /api/v1/threads/{thread_id}/session/{key} All: GET /api/v1/threads/{thread_id}/session Delete: DELETE /api/v1/threads/{thread_id}/session/{key} Clear: DELETE /api/v1/threads/{thread_id}/session ### Usage Endpoints Current: GET /v1/usage History: GET /v1/usage/history By user: GET /v1/usage/history?identifier_id=user-123 ### Usage Tier Limits | Tier | Daily Tokens | Monthly Tokens | Daily Calls | Monthly Calls | |------------|-------------|----------------|-------------|---------------| | Free | 50,000 | 500,000 | 100 | 1,000 | | Pro | 5,000,000 | 50,000,000 | 10,000 | 100,000 | | Enterprise | Unlimited | Unlimited | Unlimited | Unlimited | ### API Key Rate Limits | Tier | Requests/min | Requests/day | |--------------|--------------|--------------| | Free | 60 | 1,000 | | Professional | 300 | 10,000 | | Business | 1,000 | 100,000 | ================================================================================ ## 9. CLI REFERENCE ================================================================================ Install: ```bash curl -fsSL https://distri.dev/install.sh | sh ``` Common commands: ```bash # Run (local needs no account, just a model provider key) distri run --task "query" --agent my_agent # single task distri # interactive TUI (default agent) distri tui my_agent # TUI for a specific agent distri serve # local API server on :7777 # Auth + deploy to Distri Cloud distri login # authenticate distri push # sync agents/ skills/ templates/ (primary deploy) distri push --dry-run # preview distri checkout # pull workspace into the local layout # Per-resource pushes distri agents list distri agents push agents/my_agent.md # push one agent distri agents push agents/ --all # push all agents in a dir distri agents delete my_agent -y # delete (skip confirm) distri skills push skills/my-skill --all distri prompts push templates/system.hbs # prompt templates are .hbs files # External skill registries distri search "pdf" distri install pdf-processing@anthropic # @ # Traces / tools / secrets distri traces list # recent runs distri traces show # span tree for a run distri tools list # available tools distri tools invoke my_tool --input '{"query":"test"}' distri secrets set KEY "value" # store a secret distri secrets list ``` Notes: - `distri run` takes `--agent NAME` and `--task "..."`; `--remote` runs in a remote browsr sandbox. - `distri push` is the primary deploy (bulk sync of agents/skills/templates); management commands are namespaced (`distri agents …`, `distri skills …`, `distri traces …`). - `distri serve` listens on :7777 by default. - There is NO `distri workflows` command, workflows run via the runtime and the @distri/core `WorkflowRunner`. Coding-agent skill (curl-able): https://distri.dev/skills/distri/SKILL.md Full CLI reference: https://distri.dev/docs/reference/cli-reference ================================================================================ ## 10. COMPLETE EXAMPLES ================================================================================ ### Example 1: Full Chat App with Backend Token Auth Backend (server.ts): ```typescript import express from 'express'; import cors from 'cors'; const app = express(); app.use(cors()); app.use(express.json()); app.post('/distri/token', async (req, res) => { try { const userId = req.user?.id || 'anonymous'; const response = await fetch('https://api.distri.dev/v1/token', { method: 'POST', headers: { 'Authorization': `Bearer ${process.env.DISTRI_API_KEY}`, 'X-Workspace-Id': process.env.DISTRI_WORKSPACE_ID!, 'Content-Type': 'application/json', }, body: JSON.stringify({ identifier_id: userId }), }); if (!response.ok) throw new Error(`Token request failed: ${response.status}`); const { access_token, refresh_token, expires_at } = await response.json(); res.json({ access_token, refresh_token, expires_at }); } catch (error) { res.status(500).json({ error: 'Failed to generate token' }); } }); app.listen(3001); ``` Frontend (App.tsx): ```tsx import { useEffect, useState } from 'react'; import { DistriProvider, Chat, useAgent } from '@distri/react'; function App() { const [tokens, setTokens] = useState<{ access_token: string; refresh_token: string; } | null>(null); useEffect(() => { fetch('/distri/token', { method: 'POST' }) .then((r) => r.json()) .then(setTokens); }, []); if (!tokens) return
Loading...
; return ( { const res = await fetch('/distri/token', { method: 'POST' }); const { access_token } = await res.json(); return access_token; }, }} > ); } function ChatPage() { const { agent, loading, error } = useAgent({ agentIdOrDef: 'support_agent' }); const [threadId] = useState(() => crypto.randomUUID()); if (loading) return
Loading agent...
; if (error) return
Error: {error.message}
; if (!agent) return null; return ; } export default App; ``` ### Example 2: Chat with Custom Tools and Dynamic Context Agent definition (agents/support_agent.md): ```md --- name = "support_agent" description = "Customer support agent with product knowledge" max_iterations = 10 [model_settings] model = "gpt-4.1-mini" temperature = 0.5 max_tokens = 1500 [tools] external = ["search_products", "get_order_status", "create_ticket"] builtin = ["final"] --- # ROLE You are a helpful customer support agent. # CURRENT CONTEXT {{user_context}} # TASK {{task}} {{#if available_tools}} # TOOLS {{{available_tools}}} {{/if}} ``` React integration: ```tsx import { Chat, useAgent } from '@distri/react'; import type { DistriFnTool } from '@distri/core'; function SupportChat({ customerId, customerName }: { customerId: string; customerName: string; }) { const { agent } = useAgent({ agentIdOrDef: 'support_agent' }); const tools: DistriFnTool[] = [ { name: 'search_products', type: 'function', description: 'Search products in the catalog', parameters: { type: 'object', properties: { query: { type: 'string' } }, required: ['query'], }, handler: async (input) => { const res = await fetch(`/api/products?q=${input.query}`); return res.json(); }, }, { name: 'get_order_status', type: 'function', description: 'Get status of a customer order', parameters: { type: 'object', properties: { order_id: { type: 'string' } }, required: ['order_id'], }, handler: async (input) => { const res = await fetch(`/api/orders/${input.order_id}`); return res.json(); }, }, { name: 'create_ticket', type: 'function', description: 'Create a support ticket for the customer', is_final: true, parameters: { type: 'object', properties: { subject: { type: 'string' }, description: { type: 'string' }, priority: { type: 'string', enum: ['low', 'medium', 'high'] }, }, required: ['subject', 'description'], }, handler: async (input) => { const res = await fetch('/api/tickets', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ...input, customer_id: customerId }), }); const ticket = await res.json(); return `Ticket #${ticket.id} created.`; }, }, ]; if (!agent) return null; return ( ({ dynamic_sections: { user_context: `Customer: ${customerName} (ID: ${customerId})`, }, })} /> ); } ``` ### Example 3: Browser Automation with Transient Parts Agent definition (agents/browsr.md): ```md --- name = "browsr" description = "Browser automation agent" max_iterations = 30 [model_settings] model = "gpt-4.1-mini" temperature = 0.2 [tools] external = ["*"] builtin = ["final"] [browser_config] enabled = true headless = true --- # ROLE You are a browser automation agent. # CORE LOOP 1. Read the browser state from the observation 2. Decide the next action 3. Execute one browser command per step 4. Evaluate the result # TASK {{task}} {{#if available_tools}} # TOOLS {{{available_tools}}} {{/if}} ``` Browser step tool with transient parts: ```typescript import type { DistriFnTool, DistriPart } from '@distri/core'; function createBrowserStepTool(sessionId: string): DistriFnTool { return { name: 'browser_step', type: 'function', description: 'Execute browser commands', parameters: { type: 'object', properties: { commands: { type: 'array', items: { type: 'object', properties: { command: { type: 'string', enum: ['navigate_to', 'click', 'type_text', 'scroll_to'], }, data: { type: 'object' }, }, required: ['command'], }, }, }, }, handler: async (input): Promise => { const res = await fetch('/browser_step', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ session_id: sessionId...input }), }); const result = await res.json(); // Return transient parts, included in context but NOT saved return [ { part_type: 'text', data: `URL: ${result.url}\n${result.summary}`, __metadata: { save: false }, }...(result.screenshot ? [{ part_type: 'image' as const, data: result.screenshot, __metadata: { save: false }, }] : []), ]; }, }; } ``` ### Example 4: Teaching Assistant with getMetadata Agent definition (agents/teaching_assistant.md): ```md --- name = "teaching_assistant" description = "AI teacher that evaluates student writing" max_iterations = 12 [model_settings] model = "gpt-4.1-mini" max_tokens = 2000 [tools] external = ["get_lesson_state"] builtin = ["final"] --- # ROLE You are Teacher Ailynn, a warm writing teacher for primary school students. # LESSON CONTEXT {{lesson_context}} # STUDENT ANSWERS {{student_answers}} # TASK {{task}} {{#if available_tools}} # TOOLS {{{available_tools}}} {{/if}} ``` React integration with getMetadata: ```tsx function LessonChat({ lessonId, currentPage, questions, answers }) { const { agent } = useAgent({ agentIdOrDef: 'teaching_assistant' }); if (!agent) return null; return ( ({ dynamic_sections: { lesson_context: `Page ${currentPage} of lesson ${lessonId}`, student_answers: JSON.stringify(answers), }, })} /> ); } ``` ================================================================================ END OF REFERENCE ================================================================================ For the latest documentation: https://distri.dev/docs CLI reference: https://distri.dev/docs/reference/cli-reference Dashboard: https://app.distri.dev