Sessions
Sessions enable multi-turn conversations between users and trik agents through the handoff model.
The Problem
Without sessions, every interaction with a trik is a single turn:
User: Search for AI articles
Agent: [hands off to trik, trik responds, transfers back]
User: Tell me more about the second one
Agent: I don't have context from the previous conversation with that trik.With sessions:
User: Search for AI articles
Agent: [hands off to trik]
Trik: I found 3 articles about AI. Would you like details on any?
User: Tell me more about the second one
Trik: [resolves "the second one" using conversation history]How Sessions Work in v2
In v2, sessions are handoff sessions managed by the gateway. When the main agent hands off a conversation to a trik, the gateway creates a session that tracks the handoff lifecycle and logs tool activity.
1. Enable in Manifest
{
"capabilities": {
"session": {
"enabled": true,
"maxDurationMs": 1800000
}
}
}| Field | Description |
|---|---|
enabled | Turn sessions on/off |
maxDurationMs | Session timeout (default: 30 min) |
2. Handoff Session Flow
The gateway manages the entire handoff lifecycle:
Main Agent Gateway Trik Agent
│ │ │
│ talk_to_article_search│ │
├───────────────────────►│ │
│ │ createSession() │
│ │ processMessage(msg) │
│ ├─────────────────────────►│
│ │ { message, toolCalls } │
│ │◄─────────────────────────┤
│ │ │
│ User sends message │ │
├───────────────────────►│ processMessage(msg) │
│ ├─────────────────────────►│
│ │ { message, toolCalls } │
│ │◄─────────────────────────┤
│ │ │
│ (trik decides done) │ { transferBack: true } │
│ │◄─────────────────────────┤
│ summary + message │ │
│◄───────────────────────┤ │3. Message History
Conversational triks maintain their own message history through wrapAgent(). The SDK stores messages per session automatically:
import { wrapAgent } from '@trikhub/sdk';
import { createReactAgent } from '@langchain/langgraph/prebuilt';
const agent = createReactAgent({ llm, tools });
export default wrapAgent(agent);Under the hood, wrapAgent() maintains a Map<sessionId, BaseMessage[]> that accumulates messages across turns within a handoff session. Each call to processMessage() appends the user message, invokes the LangGraph agent, and captures the full history.
Reference Resolution
In v1, triks had to implement manual reference resolution with session history parsing. In v2, the trik is a full LLM agent with conversation memory, so references are resolved naturally.
When a user says “the second one” or “the healthcare article”, the trik’s LLM sees the entire conversation history and resolves references the same way any conversational AI would:
import { wrapAgent } from '@trikhub/sdk';
import { createReactAgent } from '@langchain/langgraph/prebuilt';
import { tool } from '@langchain/core/tools';
import { z } from 'zod';
import { transferBackTool } from '@trikhub/sdk';
const searchArticles = tool(
async ({ topic }) => {
// Fetch articles from API
const articles = await fetchArticles(topic);
return JSON.stringify(articles);
},
{
name: 'searchArticles',
description: 'Search for articles on a topic',
schema: z.object({ topic: z.string() }),
}
);
const getArticle = tool(
async ({ articleId }) => {
const article = await fetchArticle(articleId);
return JSON.stringify(article);
},
{
name: 'getArticle',
description: 'Get full details for an article by ID',
schema: z.object({ articleId: z.string() }),
}
);
const agent = createReactAgent({
llm,
tools: [searchArticles, getArticle, transferBackTool],
});
export default wrapAgent(agent);The agent sees the full conversation. When the user says “show me the healthcare one”, the LLM knows from its message history which article matches.
Session Lifecycle
┌─────────────────────────────────────────────┐
│ Handoff Start │
│ Main agent calls talk_to_<trik> │
│ Gateway creates HandoffSession │
└─────────────────┬───────────────────────────┘
│
▼
┌─────────────────────────────────────────────┐
│ Handoff Active │
│ User messages routed to trik agent │
│ Gateway logs tool calls (logTemplate) │
│ Trik maintains message history via wrapAgent│
└─────────────────┬───────────────────────────┘
│
┌─────────┼─────────┬─────────────┐
▼ ▼ ▼ ▼
┌────────────┐ ┌────────┐ ┌──────────┐ ┌──────────────┐
│ transfer │ │ /back │ │ max turns│ │ trik error │
│ back │ │ (user) │ │ exceeded │ │ │
└─────┬──────┘ └───┬────┘ └────┬─────┘ └──────┬───────┘
└────────────┼───────────┼───────────────┘
▼
┌─────────────────────────────────────────────┐
│ Handoff End │
│ Session summary injected into main history │
│ Main agent regains control │
└─────────────────────────────────────────────┘A handoff ends when:
- The trik calls
transfer_back- it signals it has finished - The user types
/back- forces a return to the main agent - Max turns exceeded - safety net (default: 20 turns)
- Trik throws an error - auto transfer-back with error summary
Only one handoff can be active at a time. While a trik has the conversation, all messages go to that trik. To use a different trik, the current handoff must end first — either through transfer_back, /back, or one of the automatic mechanisms above. Triks are isolated and cannot hand off to each other directly.
Handoff Session Data
The gateway tracks structured log entries during a handoff:
interface HandoffSession {
sessionId: string;
trikId: string;
log: HandoffLogEntry[];
createdAt: number;
lastActivityAt: number;
}
interface HandoffLogEntry {
timestamp: number;
type: 'handoff_start' | 'tool_execution' | 'handoff_end';
summary: string;
}Tool execution summaries are built from logTemplate declarations in the manifest:
{
"tools": {
"searchArticles": {
"logTemplate": "Searched for '{{topic}}' - found {{count}} results",
"logSchema": {
"topic": { "type": "string", "maxLength": 50 },
"count": { "type": "integer", "minimum": 0 }
}
}
}
}When the handoff ends, the gateway builds a summary from these log entries and injects it into the main agent’s conversation history. This gives the main agent context about what happened during the handoff without exposing raw conversation content.
Cross-Session Persistence
Handoff sessions are ephemeral. They exist for the duration of a handoff and are used to build the summary injected back into the main agent.
If your trik needs to persist data across multiple handoffs (user preferences, cached results, state), use storage:
import { wrapAgent } from '@trikhub/sdk';
import { tool } from '@langchain/core/tools';
import { z } from 'zod';
// Storage is available via TrikContext
export default wrapAgent(async (context) => {
const searchArticles = tool(
async ({ topic }) => {
const articles = await fetchArticles(topic);
// Persist last search for cross-session use
await context.storage.set('lastSearch', {
topic,
articleIds: articles.map(a => a.id),
timestamp: Date.now()
});
return JSON.stringify(articles);
},
{
name: 'searchArticles',
description: 'Search for articles on a topic',
schema: z.object({ topic: z.string() }),
}
);
return createReactAgent({
llm,
tools: [searchArticles, transferBackTool],
});
});Testing Sessions
Test handoff behavior by simulating the gateway lifecycle:
import { TrikGateway } from '@trikhub/gateway';
import { InMemorySessionStorage } from '@trikhub/gateway';
it('maintains conversation across turns', async () => {
const gateway = new TrikGateway({
sessionStorage: new InMemorySessionStorage(),
});
await gateway.initialize();
await gateway.loadTrik('./path/to/trik');
// Start handoff
const sessionId = 'test-session';
const start = await gateway.startHandoff(
'article-search',
'User wants to search for AI articles',
sessionId
);
expect(start.target).toBe('trik');
// Second turn - trik has conversation memory
const turn2 = await gateway.routeMessage(
'tell me about the second one',
sessionId
);
expect(turn2.target).toBe('trik');
// User forces back
const back = await gateway.routeMessage('/back', sessionId);
expect(back.target).toBe('force_back');
expect(back.summary).toBeDefined();
});Next: Learn how to use Persistent Storage for data that survives across sessions.