Sessiontree

Session Tree Navigation

The /tree command provides tree-based navigation of the session history.

Overview

Sessions are stored as trees where each entry has an id and parentId. The "leaf" pointer tracks the current position. /tree lets you navigate to any point and optionally summarize the branch you're leaving.

Comparison with `/fork`

Feature /fork /tree
View Flat list of user messages Full tree structure
Action Extracts path to new session file Changes leaf in same session
Summary Never Optional (user prompted)
Events session_before_fork / session_fork session_before_tree / session_tree

Tree UI

├─ user: "Hello, can you help..."
│  └─ assistant: "Of course! I can..."
│     ├─ user: "Let's try approach A..."
│     │  └─ assistant: "For approach A..."
│     │     └─ [compaction: 12k tokens]
│     │        └─ user: "That worked..."  ← active
│     └─ user: "Actually, approach B..."
│        └─ assistant: "For approach B..."

Controls

Key Action
↑/↓ Navigate (depth-first order)
Enter Select node
Escape/Ctrl+C Cancel
Ctrl+U Toggle: user messages only
Ctrl+O Toggle: show all (including custom/label entries)

Display

  • Height: half terminal height
  • Current leaf marked with ← active
  • Labels shown inline: [label-name]
  • Default filter hides label and custom entries (shown in Ctrl+O mode)
  • Children sorted by timestamp (oldest first)

Selection Behavior

User Message or Custom Message

  1. Leaf set to parent of selected node (or null if root)
  2. Message text placed in editor for re-submission
  3. User edits and submits, creating a new branch

Non-User Message (assistant, compaction, etc.)

  1. Leaf set to selected node
  2. Editor stays empty
  3. User continues from that point

Selecting Root User Message

If user selects the very first message (has no parent):

  1. Leaf reset to null (empty conversation)
  2. Message text placed in editor
  3. User effectively restarts from scratch

Branch Summarization

When switching branches, user is presented with three options:

  1. No summary - Switch immediately without summarizing
  2. Summarize - Generate a summary using the default prompt
  3. Summarize with custom prompt - Opens an editor to enter additional focus instructions that are appended to the default summarization prompt

What Gets Summarized

Path from old leaf back to common ancestor with target:

A → B → C → D → E → F  ← old leaf
        ↘ G → H        ← target

Abandoned path: D → E → F (summarized)

Summarization stops at:

  1. Common ancestor (always)
  2. Compaction node (if encountered first)

Summary Storage

Stored as BranchSummaryEntry:

interface BranchSummaryEntry {
  type: "branch_summary";
  id: string;
  parentId: string;      // New leaf position
  timestamp: string;
  fromId: string;        // Old leaf we abandoned
  summary: string;       // LLM-generated summary
  details?: unknown;     // Optional hook data
}

Implementation

AgentSession.navigateTree()

async navigateTree(
  targetId: string,
  options?: {
    summarize?: boolean;
    customInstructions?: string;
    replaceInstructions?: boolean;
    label?: string;
  }
): Promise<{ editorText?: string; cancelled: boolean }>

Options:

  • summarize: Whether to generate a summary of the abandoned branch
  • customInstructions: Custom instructions for the summarizer
  • replaceInstructions: If true, customInstructions replaces the default prompt instead of being appended
  • label: Label to attach to the branch summary entry (or target entry if not summarizing)

Flow:

  1. Validate target, check no-op (target === current leaf)
  2. Find common ancestor between old leaf and target
  3. Collect entries to summarize (if requested)
  4. Fire session_before_tree event (hook can cancel or provide summary)
  5. Run default summarizer if needed
  6. Switch leaf via branch() or branchWithSummary()
  7. Update agent: agent.replaceMessages(sessionManager.buildSessionContext().messages)
  8. Fire session_tree event
  9. Notify custom tools via session event
  10. Return result with editorText if user message was selected

SessionManager

  • getLeafUuid(): string | null - Current leaf (null if empty)
  • resetLeaf(): void - Set leaf to null (for root user message navigation)
  • getTree(): SessionTreeNode[] - Full tree with children sorted by timestamp
  • branch(id) - Change leaf pointer
  • branchWithSummary(id, summary) - Change leaf and create summary entry

InteractiveMode

/tree command shows TreeSelectorComponent, then:

  1. Prompt for summarization
  2. Call session.navigateTree()
  3. Clear and re-render chat
  4. Set editor text if applicable

Hook Events

`session_before_tree`

interface TreePreparation {
  targetId: string;
  oldLeafId: string | null;
  commonAncestorId: string | null;
  entriesToSummarize: SessionEntry[];
  userWantsSummary: boolean;
  customInstructions?: string;
  replaceInstructions?: boolean;
  label?: string;
}

interface SessionBeforeTreeEvent {
  type: "session_before_tree";
  preparation: TreePreparation;
  signal: AbortSignal;
}

interface SessionBeforeTreeResult {
  cancel?: boolean;
  summary?: { summary: string; details?: unknown };
  customInstructions?: string;    // Override custom instructions
  replaceInstructions?: boolean;  // Override replace mode
  label?: string;                 // Override label
}

Extensions can override customInstructions, replaceInstructions, and label by returning them from the session_before_tree handler.

`session_tree`

interface SessionTreeEvent {
  type: "session_tree";
  newLeafId: string | null;
  oldLeafId: string | null;
  summaryEntry?: BranchSummaryEntry;
  fromHook?: boolean;
}

Example: Custom Summarizer

export default function(indusagi: HookAPI) {
  indusagi.on("session_before_tree", async (event, ctx) => {
    if (!event.preparation.userWantsSummary) return;
    if (event.preparation.entriesToSummarize.length === 0) return;
    
    const summary = await myCustomSummarizer(event.preparation.entriesToSummarize);
    return { summary: { summary, details: { custom: true } } };
  });
}

Error Handling

  • Summarization failure: cancels navigation, shows error
  • User abort (Escape): cancels navigation
  • Hook returns cancel: true: cancels navigation silently