SESH - session tracking that survives rabbit holes

25 min read

This is the first post in a series where I share blueprints instead of code. That post explains why. This one shows what it looks like in practice.

sesh is a session management CLI. It tracks what you’re working on, captures ideas without breaking flow, and links everything together so an AI agent can traverse your work. Up, down, sideways, across projects. Instead of isolated notes, you get a navigable map that any agent can crawl. I use it every day.

The problem

Every new Claude Code session starts from zero. The agent doesn’t know what you decided yesterday, what’s blocked, or what you were about to try next.

I tried solving this with notes. Docs in various places, sometimes in the repo, sometimes in a scratch folder. The docs sprawled. No structure connecting related work, no way to see what was active versus done versus blocked.

But the real problem is rabbit holes. You start on a feature, discover a dependency needs upgrading, go down that hole, find a bug in the dependency, go down that hole. Three levels deep and you’ve forgotten what you were originally doing. I needed a way to create a quick doc for each level, link them together, and crawl back up.

That’s what made me build sesh. A CLI that creates structured session docs, links them together, and is fast enough that an agent can do it in one command without burning context on file creation.

What it actually feels like

I never type sesh commands. The agent does. I use it with Claude Code and Codex, and the CLI exists so any AI agent has a clean interface. I’m just talking.

What it actually feels like

Pick a scenario. Watch it play out.

When Claude gets tripped up or confused by something, I improve the system. Better error messages, clearer help output, smoother edge cases. Then it works next time. A shared Claude rule loads into every project automatically, so sesh is ready the moment I open any repo. No per-project setup, no onboarding. It just works everywhere.

The contents of a session doc are deliberately unstructured. The frontmatter tracks status, dates, and relationships. Just enough to traverse and search. But the body? Whatever the agent writes. Decisions, dead ends, todo lists, half-formed thoughts. It doesn’t matter how it’s organized. What matters is that the raw context is written down somewhere, and the agent knows how to go back and find it.

It’s also portable. I run sesh from Claude Code, Codex, and background agents that handle long-running workflows. One thing I’ve been doing recently is using sessions to pass work between tools. Have Claude sketch a plan in a session doc, then hand it off to Codex for execution, or vice versa. The session is the shared context layer. Neither tool needs to know about the other. They just read and write the same file.

If any of those tools disappears tomorrow, my sessions are still markdown files in git. There’s no vendor lock-in because there’s no vendor. It’s just text.

This is a deliberate philosophy: sessions live in your codebase, checked into git, right next to the code they describe. Claude Code has its own planning features, but those plans live outside your repo. Ephemeral, not version-controlled, gone when the session ends. Sesh sessions are the opposite. They’re part of your living codebase. In some ways they’re more important than the code itself, because the code shows you what was built but the session docs show you why.

The architecture

Everything is a markdown file with YAML frontmatter, stored in .sessions/ at the project root.

.sessions/
├── 2026-02-24_auth-refactor.md        # Active session
├── 2026-02-24_cache-strategy.md       # Idea (just a lightweight session)
├── 2026-02-20_api-redesign.md         # Completed session
└── SESSION_INDEX.md                   # Auto-maintained table of contents

A session file looks like this:

---
date: '2026-02-24'
created_at: '2026-02-24T10:30:00-05:00'
updated_at: '2026-02-24T14:15:00-05:00'
status: in_progress
description: "Refactor auth to use JWT instead of session cookies"
parent: null
blocked_by: null
related: []
---

# Auth refactor

## Goal

Replace session cookies with JWT. Current auth breaks when
we add the mobile API.

## Decisions

- Going with short-lived access tokens + refresh tokens
- Tried opaque tokens first, but JWT lets the API gateway
  validate without hitting the auth service

## Todo

- [x] Swap session middleware for JWT verification
- [ ] Add refresh token rotation
- [ ] Update mobile client

The body is freeform markdown. No enforced structure. Some sessions are three lines, some are pages of notes and decisions. The tool doesn’t care, and neither should you. A lot of people get stuck trying to make the structure perfect and consistent. I don’t spend any time on that. The session doc is a context dump. Its job is to capture what happened efficiently enough that an LLM can read it later and get up to speed. That’s it.

And the session grows as you work. I say “append that to the sesh” constantly. Not “rewrite the plan” or “update the status doc.” Append. The distinction matters. Most planning tools assume a straight line: you plan up front, then execute. I don’t work that way. I’m constantly getting into something, discovering new constraints, and updating the plan as I go. A session doc is the running record of that. Decisions accumulate, dead ends get noted, the plan evolves. My Claude rules explicitly say to preserve the history and journey, not to rewrite sections to reflect current state. The path you took is the valuable part.

The frontmatter is the key design choice. YAML frontmatter gives you the perfect balance of structured and flexible. The status, dates, and relationships are structured enough to query and index. The body is freeform enough to capture whatever you need. And frontmatter is a format that every static site generator, every markdown tool, and every AI agent already knows how to parse. You don’t have to teach anything how to read it.

Why files, and why checked into git

Sessions are mostly prose. Notes, decisions, todos, context for future you. Markdown is the natural format for that.

But the bigger reason is that sessions are checked into git on purpose. This isn’t a side effect of using files. It’s the point.

Six months from now, when someone asks “why did we build the auth system this way?”, the answer is in the session doc. The decisions, the alternatives you considered, the dead ends you walked down before finding the right path. git log --since="last February" -- .sessions/ shows you every session from that period. git blame .sessions/2026-02-24_auth-refactor.md shows when each decision was recorded.

This has already paid for itself. I’ve gone back to completed sessions months later and found the exact reasoning behind a design choice that I’d completely forgotten. Not the code, which shows you what was built, but the session doc, which shows you why. The commit history on .sessions/ is a decision log for the whole project.

Files also mean no migration runner. When I wanted to add a new field, I just started putting it in new session frontmatter. Old sessions without it work fine. No schema changes, no migration files.

And AI agents can read and edit files directly. Claude Code can open a session doc, read the context, and update the notes without going through an API. The files are the API.

This is also how you survive context compaction. AI coding tools compress older messages as the conversation grows, and when that happens, decisions and context from earlier in the session just vanish. But the session doc is still there on disk with everything you wrote down. The agent re-reads it and picks up where it left off. You can wire this up with a hook that automatically re-reads the session after compaction, or just say “re-read the sesh” when things feel off. Either way, the session doc is the bridge between conversations that would otherwise be completely disconnected.

Design decisions worth stealing

Auto-save with smart git integration

After every command that changes a file, sesh auto-commits the .sessions/ directory. But it’s not a dumb git add . && git commit. Three rules make it work:

Skip if the user has non-session files staged. If you’re in the middle of staging a feature commit and you run sesh complete, the tool shouldn’t mix your session bookkeeping into your feature commit. It checks for staged files outside .sessions/ and backs off if it finds any.

Squash consecutive sesh commits. If you run sesh new, then sesh idea, then sesh link in quick succession, you don’t want three commits that say “sesh: update sessions.” The auto-save amends the previous commit if it was also a sesh commit and only touched .sessions/ files.

Build meaningful commit messages from session names. Instead of “update sessions,” the commit says “sesh: complete auth-refactor, new cache-strategy.” It extracts the session names from the changed filepaths.

This is the biggest time saver when you have multiple agents working on the same repo. Each agent is creating sessions, capturing ideas, completing work, and none of that clutters your git history or collides with your feature commits. Session management becomes invisible bookkeeping.

Atomic operations

Every mutating command follows the same pattern: validate input, find the target session, update the session file, update the INDEX, print feedback, auto-save. If any step fails, the whole operation fails. No half-updated INDEX files, no sessions that changed status but didn’t get logged.

This matters more than it sounds. Session management is background work. You’re doing it between writing code and running tests. If you have to worry about whether sesh complete actually worked, you’ll stop using it.

There’s a practical benefit too: the CLI handles the file creation, frontmatter, and index update in one command. An AI agent doesn’t have to make three separate edits to create a session. sesh new "feature" does everything. sesh complete "feature" does everything. The fewer tool calls an agent needs for bookkeeping, the more of its context window stays focused on actual work.

Relationships as plain strings

Sessions have two relationship types, both stored as filename strings in frontmatter:

Parent-child: parent: 2026-02-20_api-redesign.md in the child’s frontmatter. sesh tree renders the whole hierarchy. This is the mechanism behind --child-of for rabbit holes.

Peer links: The related array holds bidirectional links. These can reference sessions in the same project or across projects using the format other-project:2026-02-24_cli-update.md. When I’m working on a CLI tool in one repo and it needs changes to a shared library in another, I link the sessions across repos. sesh around shows any session’s full neighborhood: parent, children, and peers.

No graph database. Just strings in YAML arrays, resolved through a project registry at ~/.config/sesh/projects.json. Covers everything I’ve needed.

Ideas without breaking flow

You’re deep in an auth refactor. You notice the caching layer is wrong. You don’t want to stop what you’re doing, but you don’t want to forget it either.

sesh idea "caching layer is wrong, API responses should expire after 5min not 1hr" creates a lightweight session file and gets out of your way. No title prompt, no template, no context switch. The whole interaction takes two seconds. You’re back in the auth refactor.

Ideas are just sessions. Same frontmatter, same flat directory, just a different status. When you’re ready to work on one, sesh start "caching-layer" flips it to active.

This matters because the cost of capturing a thought needs to be near zero. If it requires any ceremony, you’ll keep the thought in your head and lose it when the context window compacts.

Child sessions for rabbit holes

The other pattern that comes up constantly: you’re working on a feature and you hit something that needs its own focused work. A dependency needs upgrading, or you discover a bug that’s blocking your feature, or a sub-problem turns out to be bigger than you thought.

sesh new "upgrade-jwt-library" --child-of auth-refactor creates a child session linked to the parent. You dive into the rabbit hole with its own session doc, its own notes, its own todo list. When you’re done, sesh complete "upgrade-jwt-library" and you climb back up to the parent session. The parent-child relationship is recorded, so later you can see exactly how the work decomposed.

sesh around "auth-refactor" shows you the neighborhood:

● auth refactor (you are here)

↓ Children:
  ✓ upgrade jwt library (2026-02-24)
  ● mobile api integration (2026-02-25)

↔ Related:
  ● api redesign (2026-02-20)

This is how real work actually happens. Not linear, not one task at a time. You go down rabbit holes, come back up, branch off, link things together. The session graph reflects that.

Every project has its own .sessions/ directory. sesh finds it by walking up to the git root. A global Claude Code rule (loaded from ~/.claude/rules/global/) teaches every agent how to use sesh from the start. No per-project setup.

You register projects once and can search across all of them, link sessions across repos, and list what’s active everywhere. sesh search "auth" --all uses ripgrep under the hood. Fast, because it’s just grepping files.

For deeper search, sessions auto-index into qmd, a local search engine that combines keyword matching and vector embeddings. “Find sessions where I was debugging performance issues” works even if “performance” doesn’t appear in the doc.

The domain docs

This is the part that matters most if you’re rebuilding. The CLAUDE.md that teaches an AI agent how sesh works:

# sesh - Session Management

Track development work with session docs.

Use CLI for lifecycle ops. Run `sesh --help` for full
command reference.

Session docs preserve the journey. They're historical records.
When updating, keep the history and append progress rather
than rewriting sections. Check off todos, add outcome notes,
update status, but don't remove the path that got there.

| Want to... | Command |
|------------|---------|
| Start new work | sesh new "feature-name" |
| Start with context | sesh new "feature" --description "Context" |
| Start child session | sesh new "child" --child-of parent |
| Finish work | sesh complete "feature" --summary "Done" |
| Mark as blocked | sesh block "feature" --reason "waiting on X" |
| Resume blocked | sesh resume "feature" |
| View session | sesh show "feature" |
| See what's active | sesh status or sesh list |
| See relationships | sesh tree |
| See neighbors | sesh around "feature" |
| Capture idea | sesh idea "description" |
| Start working on idea | sesh start "idea-name" |
| Get file path (for handoffs) | sesh path "feature" |
| Search across projects | sesh search "query" |

The key constraint in that doc is “session docs preserve the journey.” Without it, AI agents will rewrite session files to reflect current state, erasing the history of how you got there. The decisions and dead ends are the valuable part. The instruction to append instead of rewrite is what keeps them.

The starter prompts

Three prompts to go from zero to working session management.

1. Build the CLI

Build a CLI tool called "sesh" for managing development sessions.

Core concept: sessions are markdown files with YAML frontmatter
stored in .sessions/ at the project root.

Session file format:
- Filename: YYYY-MM-DD_slug-name.md (date prefix for sorting)
- YAML frontmatter: date, created_at, updated_at, status
  (in_progress|blocked|complete|paused|idea), description,
  parent, blocked_by, related[]
- Markdown body: freeform notes

Commands needed:
- init (create .sessions/ directory)
- new "name" [--description "text"] [--child-of parent] [--related name]
- complete "name" [--summary "text"] (appends summary to body)
- block "name" --reason "text"
- resume "name"
- idea "description" [--related name] (lightweight session with status: idea)
- start "idea" (flip idea to active session)
- list (show all sessions grouped by status)
- status (quick view of active work)
- show "name" (full session details)
- path "name" (print file path, useful for handoff prompts)
- tree (parent-child hierarchy visualization)
- around "name" (show parent, children, and related)
- search "query" (ripgrep across session files)
- link "a" "b" (bidirectional peer link)

Key behaviors:
- Find .sessions/ by walking up from cwd to git root
- Auto-commit .sessions/ changes after mutations
- Skip auto-commit if user has non-session staged files
- Squash consecutive sesh commits
- Maintain SESSION_INDEX.md (table of active/blocked/completed)
- Relationships: parent field for hierarchy,
  related[] for peer links

Design constraints:
- File-based state only, no database
- Every operation is atomic (file + index update together)
- Session body is freeform, never enforced
- Ideas are zero-friction (no prompts, no templates)
- Git-friendly (all state in diffable text files)

2. Set up the Claude rule

Create a global Claude Code rule for sesh at
~/.claude/rules/global/sesh.md so every project gets
session management automatically. The rule should teach
the agent the key commands and one critical constraint:
session docs preserve the journey. Append progress,
don't rewrite history.

3. First session

Run sesh init in this project, then create a session for
whatever we're working on right now.

That’s it. The first prompt gets you the tool, the second makes it available everywhere, the third gets you using it. Grow it from there.

What else is out there

claude-sessions handles basic session start/update/end with slash commands. Markdown Projects is the closest architecturally, with file-based issue tracking with YAML frontmatter, stored in git. Conductor from Google takes a spec-driven approach with persistent markdown files for requirements and tasks.

Where sesh diverges: the rabbit-hole workflow (child sessions, climbing back up), zero-friction idea capture, and cross-project linking.

Start here

The file-based approach means you can start with five lines of bash that create timestamped markdown files and grow from there. That’s how sesh started. The architecture above is where it ended up after months of daily use.

The architecture docs and starter prompts are also on GitHub if you want to grab them directly.