DorkOS
Concepts

Sessions

What a DorkOS session is, how it stays in sync across tools, and how it works under the hood

Sessions

What a session is

A session is one conversation with one agent. Every message you send, every file the agent touches, and every reply you get belongs to that session.

DorkOS runs sessions on three different agent tools, Claude Code, Codex, and OpenCode, and shows them all in a single list. You stop hunting across terminals and windows to remember what you were doing.

Here's the quick win: start a chat in the DorkOS web app, and if it's a Claude Code session, you can also open that exact conversation from the claude CLI in the same folder. Same conversation, either window. DorkOS just gives every session an ID so it can find it again later.

How to work with sessions day to day

Only one place can send a message at a time. If you have a session open in two windows, DorkOS locks it to whoever's actively using it, and unlocks it again a few minutes after they stop, in case you wandered off mid-turn. Everyone can still watch the conversation; only sending a new message needs the lock.

Approvals survive a refresh. If an agent asks to approve running a command or editing a file, that prompt sticks around even if you reload the page, switch sessions, or open a second window. It picks up right where it left off.

Sessions from other tools show up automatically. Claude Code sessions are just files on your disk, so any tool that can read those files sees the same sessions. Start a task from the claude CLI and it appears in the DorkOS sidebar too, no import step, no sync button. The Obsidian plugin can read the same files as well, though it's still early and under active testing, so expect some rough edges there.

Codex and OpenCode sessions work the same way in spirit: each tool keeps its own session store, and DorkOS reads from all three to build one combined list. If DorkOS can't reach one of them, it still shows you the others and flags which one it missed.

A "New messages" line remembers where you stopped. Come back to a chat that ran on without you and DorkOS marks the spot. Everything you have not seen sits below the line.

That mark belongs to you rather than to one browser, so reading a chat on your laptop moves it on your phone too, with no reload. It is also private: nobody else can see what you have read, and neither can your agents. DorkOS has no "seen by" list and no read receipts. Chats used to keep this mark in a single browser, and that old copy is thrown away the next time you open a chat there, so the line may sit further back than you expect once. Reading the chat sets it straight.

See how full each session's context is. Every row in the list shows a small gauge of how much of its context window is used, so you can spot which agents are running low before they slow down. A line at the top of the list sums it up, "2 near full · 1 auto-compacted", so you know which one to jump into first. Claude Code sessions show this reading even when they're closed; Codex and OpenCode show it once you open the session, and read "unknown" until then, because those tools only report context usage while a session is live.

Want a picture of this? A short clip of a session appearing in the DorkOS sidebar the instant you start it from the CLI says more than the paragraph above ever could.

Reference (for developers)

If you're calling the API directly or debugging DorkOS itself, here's the exact mechanics behind everything above.

Creating and messaging a session

A session is created with a unique ID, a working directory, and a permission mode (whether tool calls need your approval). Sending a message triggers a turn: POST /api/sessions/:id/messages returns 202 Accepted immediately, and the actual response streams over the session's durable event stream, GET /api/sessions/:id/events. That stream carries text deltas, tool calls, approval requests, and task-progress updates in real time.

The 202 means the server has your message. If the session was already working, the message waits on the session's queue and runs when the current turn ends; the response says which happened, and queuePosition is 1 when nothing was ahead of it.

One turn at a time

A session runs one turn at a time, and the server keeps a queue of what is waiting. Anything sent to a session that is already working joins that queue instead of being refused, whichever window or client sent it. The queue lives on the server, so every client sees the same one, any of them can reword or withdraw a message before it runs, and nothing is lost to a page refresh or a restart.

The X-Client-Id header identifies a client across reconnects and labels the messages it queued. A write lock still guards a session so two turns cannot run on it at once, but it is internal now: it is bound to the turn, released when the turn finishes or errors, and expires after 5 minutes of inactivity as a backstop.

Storage

DorkOS keeps no separate session database of its own. Each runtime owns its storage:

  • Claude Code writes each session as a transcript file at ~/.claude/projects/{project-slug}/{session-id}.jsonl, in JSONL (one JSON record per line). That file is the single source of truth for the session.
  • Codex and OpenCode keep their own session stores, managed by their own tooling.

DorkOS reads the Claude Code transcript to build the session list, plus per-session metadata and full message history. Metadata is computed fresh on every request, so it always reflects the current file on disk.

Session sync

Each connected client subscribes to GET /api/sessions/:id/events. On connect, the server sends a snapshot: completed history, the in-progress turn, session status, and any pending approvals. From there, every client receives the same live events with monotonic (always-increasing) sequence numbers, including turns triggered by another client or the CLI.

On reconnect, the client resumes from its last sequence number via the Last-Event-ID header, and the server replays exactly what it missed. If that gap can't be served, the server falls back to sending a fresh snapshot instead.

The sidebar list stays live the same way: a single global stream, GET /api/events, announces session_upserted, session_removed, and session_status events as sessions are created, deleted, or change state, across every client and the CLI.

Read state

How far you have read in a session is stored server-side, in the same table rooms use, keyed by (user, thread kind, thread id). Two endpoints cover it:

MethodPathPurpose
PUT/api/read-cursors/session/:idMove your mark to a position.
GET/api/read-cursors/session/:idRead your mark back.

The stored position counts the messages the server has confirmed, not durable-stream sequence numbers, so it survives a reconnect and a replay. Both endpoints are people-only: a caller resolved as an agent gets 403 PEOPLE_ONLY, and no request can name a user other than the caller. A cursor that actually moves is broadcast as a read_cursor event on the global GET /api/events stream, which is what updates a second window without polling.

In Obsidian there is no server to hold the mark, so it is kept in that browser profile instead. The "New messages" line still works there; it just does not travel to your other devices.

Session metadata

Most people never need this. Here's where each field on a Claude Code session comes from, if you do:

FieldSource
IDThe unique ID in the JSONL filename
TitleFirst user message in the transcript
PreviewBeginning of the last assistant message
CreatedFile creation timestamp
UpdatedFile modification timestamp
Permission modeExtracted from the SDK's init message

Working directories

Each session runs inside a working directory that determines where the agent can read and write files. It's set when the session is created, persisted in the URL as the ?dir= query parameter in standalone mode, and validated against a configurable boundary so the agent can't wander outside allowed paths. The directory picker in the DorkOS sidebar lets you browse and choose one before you start a session.

Opening a chat with the question already written

A link can carry the first message for you. Add ?prompt= to a session URL and DorkOS opens a new chat with those words already in the box, ready for you to read, edit, or send:

http://localhost:4242/session?dir=/path/to/project&prompt=what%20changed%20this%20week

The text has to be URL-encoded — spaces become %20, and most tools will do this for you.

Add &send=1 and DorkOS sends it for you as soon as the chat opens, so the agent is already working when you look at the screen. It sends once. Refreshing the page, or going back to it, will not send it again — the link drops both settings from the address the moment they're used.

This is for starting a conversation, never for joining one. If the link points at a chat that already has messages in it, the prompt is ignored: nothing is typed and nothing is sent. It is also ignored if you've already started typing, so a link can never overwrite your own words.

Next Steps