---
title: "Sessions"
description: "How mikan separates platform chat history from the agent harness's structured sessions."
url: "https://geminixiang.github.io/sessions/"
---

# Sessions

`log.jsonl` keeps human-readable platform messages used to bootstrap thread or reply context.
    `sessions/*.jsonl` stores tool results and agent turns so the mikan agent harness can continue
    work.
    Threads, reply chains, and shared channels map to fixed session files so different conversations
    do not contaminate each other.

## Platform session model

A session key is `conversationId` on its own, or `conversationId:suffix` for a scoped session. The suffix is whatever the platform uses to identify a thread or reply target.

| Platform | `sessionKey` rule                                                                                                    | Notes                                                                         |
| -------- | -------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------- |
| Slack    | top-level and DM: `channelId`; thread (including in a DM): `channelId:threadTs`                                      | thread sessions are fixed files bootstrapped from recent chat history         |
| Discord  | DM and shared top-level: `channelId`; thread: `parentChannelId:threadChannelId`; reply: `channelId:repliedMessageId` | a guild thread is addressed by its parent channel plus the thread's own id    |
| Telegram | private chat: `chatId`; shared top-level: `chatId:messageId`; shared reply: `chatId:repliedMessageId`                | no native thread model, so the message replied to is the scope                |
| GitHub   | `GH_<owner>_<repo>_<number>`                                                                                         | one issue or pull request is one flat session; inline review threads included |

## Session keys and offices

Session keys stay raw platform values — the grammar in `src/sessions/session-key.ts` never sees an office key. Runtime state is addressed by the conversation's office plus its session key, so two platforms that share a raw conversation id can never select each other's runners, queues, or stop state. Storage paths follow the office: session files live under `<workspace>/<officeKey>/sessions/`.

## Session identity safety

A session key always belongs to exactly one conversation. Platform adapters may supply a scoped key, but conversation intake and the runtime reject a key whose conversation prefix differs from the incoming conversation. Conversation identities and scoped suffixes also reject path separators, control characters, and the special `.` / `..` segments before they can influence session storage. Thread session files keep their existing names and persistence behavior; the sessions module additionally rejects symlink session targets and proves derived paths remain inside the office's `sessions/` directory.

## Files

- workspace/
  - v1-slack-c0aaaaaa1-…/ one office
    - **log.jsonl** platform message history
    - sessions/
      - **current** points to the active top-level session
      - **\*.jsonl** structured harness session context, including tool results
      - **scope-derived files** fixed session files for thread / reply scopes

## Thread lineage

A scoped session records the top-level session it was created from in its header, as `parentSession` (path) and `parentSessionId` (uuid). The link survives a reset: resetting a scoped session rebuilds its file but keeps the original parent rather than re-binding to whichever top-level session is current now. Sessions written before lineage metadata existed are left alone rather than recreated.

## Bootstrap, rotation, reset, and Session Dream

A new scoped session is seeded from at most 200 applicable platform messages from the previous 14 days. Shared top-level channel sessions rotate on biweekly Sunday boundaries. Rotation is lazy rather than cron-driven: mikan checks the current session timestamp when the first applicable message arrives after a boundary.

Before `/new` resets a private conversation, or before a biweekly shared-channel rotation changes the active session, mikan runs a hidden **Session Dream**. It reviews the old structured transcript and preserves only durable decisions, preferences, facts, and ongoing work in that conversation's `MEMORY.md`. The Dream can read context but can modify only the conversation-specific memory file; it cannot modify the workspace-global `MEMORY.md` or promote information into other conversations.

```text
old session → Session Dream → conversation/MEMORY.md → new session → current message
```

If Session Dream fails or is blocked, mikan keeps the old session active instead of rotating or resetting it. `/new` reports the failure to the user; automatic rotation logs the failure, handles the message in the old session, and may try again on a later message. A successful rotation changes the active structured context file but does not delete older JSONL files.

  Use `new` / `/new` in a private chat to Dream and reset the current session. Unless you remove
  them manually, previous session files stay on disk for inspection.
