Agent DailyAgent Daily
tutorialintermediate

Building a session browser Mar 2026 • Claude Agent SDK Agent Patterns List, read, rename, tag, and fork Agent SDK sessions on disk to build a conversation history sidebar without writing a transcript parser.

cookbook
View original on cookbook

The Claude Agent SDK provides built-in session management functions to build conversation history sidebars without writing custom parsers. This cookbook demonstrates how to list, read, rename, tag, and fork sessions stored as JSONL transcripts on disk. By leveraging SDK primitives like list_sessions(), get_session_messages(), and rename_session(), developers can implement session browsers for desktop apps, IDE extensions, and chatbots without managing file I/O directly.

Key Points

  • Use list_sessions(directory=...) to scan project transcripts and retrieve metadata (summary, timestamp, branch, tag) sorted by recency—no API calls or subprocess spawning required
  • Call get_session_messages(session_id, directory=...) to reconstruct full message chains from JSONL transcripts for rendering in chat UI without re-running the agent
  • Set cwd option in ClaudeAgentOptions to organize all sessions under a dedicated project bucket (~/.claude/projects/<encoded-cwd>/<session-id>.jsonl)
  • Implement pagination with limit and offset parameters on list_sessions() to efficiently handle large conversation histories in sidebar components
  • Use rename_session() and tag_session() to append metadata to transcripts, enabling user-driven organization without rewriting files
  • Fork sessions at any point using fork_session() to branch conversations and resume forks as live query() calls, preserving original history
  • Leverage get_session_info() for single-session lookups when you already have a session ID stored in your database
  • Design session pickers to load first page on open, then fetch additional pages as users scroll—matching the pattern used in Claude Code Desktop and VS Code extension
  • Keep token usage low during demo/testing by setting max_turns=1 and allowed_tools=[] for single-turn conversations
  • All session management is file-based and synchronous—no agent loop required to read, organize, or display past conversations

Found this useful? Add it to a playbook for a step-by-step implementation guide.

Workflow Diagram

Start Process
Step A
Step B
Step C
Complete
Quality

Concepts

Artifacts (6)

Session Browser Setuppythonscript
import os
from pathlib import Path
from dotenv import load_dotenv

load_dotenv()
MODEL = "claude-haiku-4-5"

# All demo sessions live under this project directory
DEMO_DIR = str(Path("session_browser_demo").resolve())
os.makedirs(DEMO_DIR, exist_ok=True)
print(f"Demo project dir: {DEMO_DIR}")
Create Demo Sessionspythonscript
from claude_agent_sdk import ClaudeAgentOptions, ResultMessage, query

async def run_one_turn(prompt: str) -> str:
    """Run a single-turn conversation and return its session_id."""
    opts = ClaudeAgentOptions(
        model=MODEL,
        cwd=DEMO_DIR,
        max_turns=1,
        allowed_tools=[],  # text-only, no tool loop
    )
    session_id = None
    async for msg in query(prompt=prompt, options=opts):
        if isinstance(msg, ResultMessage):
            session_id = msg.session_id
            preview = (msg.result or "")[:80]
            print(f"[{session_id[:8]}] {preview}...")
    if session_id is None:
        raise RuntimeError("No ResultMessage received; check API key and SDK version.")
    return session_id

prompts = [
    "Give me three name ideas for a CLI tool that manages git worktrees.",
    "Explain the difference between a mutex and a semaphore in one paragraph.",
    "Write a haiku about merge conflicts.",
]

demo_session_ids = []
for p in prompts:
    sid = await run_one_turn(p)
    demo_session_ids.append(sid)
print(f"\nCreated {len(demo_session_ids)} sessions.")
List and Render Sessionspythonscript
from datetime import datetime
import pandas as pd
from claude_agent_sdk import SDKSessionInfo, list_sessions

sessions = list_sessions(directory=DEMO_DIR)

# Render as a table
rows = []
for s in sessions:
    rows.append({
        "id": s.session_id[:8],
        "summary": (s.summary[:50] + "...") if len(s.summary) > 50 else s.summary,
        "modified": datetime.fromtimestamp(s.last_modified / 1000).strftime("%H:%M:%S"),
        "branch": s.git_branch or "-",
        "tag": s.tag or "-",
    })
pd.DataFrame(rows)

# Pagination example
page_2 = list_sessions(directory=DEMO_DIR, limit=20, offset=20)
Read Session Messagespythonscript
from claude_agent_sdk import get_session_messages, get_session_info

# Get session info
info = get_session_info(demo_session_ids[0], directory=DEMO_DIR)
print(f"Session: {info.session_id}")
print(f"Summary: {info.summary}")
print(f"First prompt: {info.first_prompt}")
print(f"Created: {datetime.fromtimestamp(info.created_at / 1000)}")
print(f"Size: {info.file_size:,} bytes")

# Get and display messages
messages = get_session_messages(demo_session_ids[0], directory=DEMO_DIR)
for m in messages:
    role = m.type
    text_parts = [b.get("text", "") for b in m.message.get("content", []) if isinstance(b, dict) and b.get("type") == "text"]
    text = " ".join(text_parts).strip()
    print(f"[{role:>9}] {text[:100]}")

# Pagination for long sessions
window = get_session_messages(demo_session_ids[0], directory=DEMO_DIR, limit=50, offset=0)
Organize Sessions with Titles and Tagspythonscript
from claude_agent_sdk import rename_session, tag_session

# Rename a session
rename_session(
    session_id=demo_session_ids[0],
    title="CLI Tool Naming Ideas",
    directory=DEMO_DIR
)

# Tag a session for organization
tag_session(
    session_id=demo_session_ids[1],
    tag="concurrency",
    directory=DEMO_DIR
)

# List sessions again to see updated metadata
sessions = list_sessions(directory=DEMO_DIR)
for s in sessions:
    print(f"Title: {s.custom_title or s.summary[:40]}")
    print(f"Tag: {s.tag or 'untagged'}")
Fork a Sessionpythonscript
from claude_agent_sdk import fork_session, query, ClaudeAgentOptions

# Fork a session at a specific message index
forked_session_id = fork_session(
    session_id=demo_session_ids[0],
    fork_at_message_index=0,  # Fork after first user message
    directory=DEMO_DIR
)

print(f"Forked session ID: {forked_session_id}")

# Resume the fork as a live query
opts = ClaudeAgentOptions(
    model=MODEL,
    cwd=DEMO_DIR,
    session_id=forked_session_id,
)

async for msg in query(
    prompt="Can you add a few more ideas?",
    options=opts
):
    if isinstance(msg, ResultMessage):
        print(f"Fork response: {msg.result[:100]}...")