Chat & Sessions
How a message becomes a turn: the session model, the dashboard chat pipeline,
persistence, and the memory-privacy modes. Paths are relative to
PersonalClaw/src/personalclaw/.
Session model
Section titled “Session model”A session is one conversation thread, whatever surface it lives on (dashboard chat, channel thread, loop worker, webhook, subagent).
-
session.py—SessionManager. Owns live session state. Each session has a FIFO message queue (dequeof pending messages) guarded by a semaphore, so messages arriving on the same channel thread are serialized — a turn finishes before the next queued message starts. -
session_map.py— the persistent session↔thread map. Stored at~/.personalclaw/session_map.json(atomic tmp+rename writes). Each entry carriessid,thread_ts,channel_id— generic keys, no channel-vendor shape assumed.set_channel_link/get_channel_linkare the one API for linking a dashboard session to a channel thread; a reverse index mapsthread_ts→ session key. -
session_restrictions.py— memory modes. Two restriction registries, kept in core because any surface can request them:- temporary — blank-slate thread: memory READS suppressed
(
blocks_reads) and writes suppressed. - incognito — ephemeral: memory WRITES suppressed, reads allowed.
is_restricted()(either mode) gates the after-turn learning path, session listing/search, and memory recall (see knowledge-memory.md). Restricted sessions never write lessons (after_turn_review.pycheckssession.is_restricted). - temporary — blank-slate thread: memory READS suppressed
(
-
session_workspace.py/session_pid.py— per-session working directory resolution and process-id tracking.
History & persistence
Section titled “History & persistence”history.py— one JSONL file per session at~/.personalclaw/sessions/{safe_key}.jsonl. Files rotate at 2 MB (_SESSION_MAX_BYTES); dropped lines are archived tosessions/archive/with a 7-day retention sweep (ARCHIVE_RETENTION_DAYS, rate-limited to once per hour). Archive reads are redacted throughredact_credentials/redact_exfiltration_urlsbefore anything leaves the store.resolve_history_key()resolves whether a bare key is a channel-thread key or lives in thedashboard:namespace by asking the store — core assumes no key shape and names no provider.dashboard/chat_persistence.py— the dashboard-side persistence contract over the JSONL store (message append, metadata, variants). Model-to-provider matching is data-driven viacatalog.model_family_provider_types(model)— no vendor names at the call site, and unknown model families are never restricted.
The dashboard chat pipeline
Section titled “The dashboard chat pipeline”dashboard/chat_runner.py is the turn engine. A turn flows:
- Prompt-mention expansion — a leading
@name key=valueexpands a saved prompt via_expand_prompt_mention(user prompts live at~/.personalclaw/prompts/, snippets atprompt_snippets/; the composer’s @-menu suggests prompts only at message start). - Context assembly —
context.py(ContextBuilder) builds the system context: the{{bot_name}}variable (live-resolved fromagent.bot_name), memory context, and — for channel-linked sessions — thechannel-thread-contextsnippet.context_engine.pyandcontext_compaction.pymanage sizing and compaction. - Agent resolution — the selected agent’s prompt governs. Task-mode
posture is layered as a
system_prompt_suffixON TOP of the resolved agent prompt — never a replacement (seechat_runner.pyaround thesystem_prompt_suffixcall site). - Model resolution — the
chatuse-case binding fromactive_models.json, unless the agent pins a model or the composer overrides per-session (themodelkwarg threads throughllm/registry.pyregistry.build; every factory honors it). - Streaming + persistence — chunks stream over the dashboard WebSocket; the finished turn appends to the session JSONL.
Around the engine:
dashboard/chat_handlers.py— session listing/history. Channel-linked rows carryorigin="channel"(computed at list-time from the session map, never persisted); the frontendChatPage.tsxswitches tabs on that literal.dashboard/chat_title.py— auto-title plus optional auto-tagging in ONE background LLM call (configdashboard.auto_tag_sessions);chat_retag.pyis the batch re-tag job (cancellable, board-triggered).dashboard/chat_folders.py/chat_tags.py— organization; persisted infolders.json/tags.json.dashboard/chat_channel.py— channel link/handoff routes (POST /api/chat/sessions/{session}/channel-link,GET /api/channels/reply-targets) — provider-blind, built onChannelDeliveryonly (see inbox-channels.md).dashboard/chat_voice.py—POST /api/voice/synthesize, sentence- chunked TTS throughtts.registry.active_voice_params(whatever TTS provider is bound).
Variant branching (regenerate)
Section titled “Variant branching (regenerate)”dashboard/chat_regenerate.py: regenerating an assistant message preserves
the prior answer as a variant. The message’s variants[] list (capped at
_MAX_VARIANTS) plus variant_idx are persisted in the session JSONL, so the
user can flip between alternative answers and the choice survives reload. The
backend broadcasts variant switches; the frontend renders prev/next navigation
on the message.
Forking
Section titled “Forking”dashboard/chat_fork.py — POST /api/chat/sessions/{session}/fork copies a
session into a new tab. App-scoped callers may only fork sessions they own
(the app claim is checked; unscoped sessions are denied to apps).
Channel-linked sessions
Section titled “Channel-linked sessions”A dashboard session can be linked to a channel thread (and vice versa):
- Linking goes through core
session_map.set_channel_link— the channel app never touches the map file directly. sync_bridge.pyimplements the dashboard↔channel handoff (handoff_to_channeloverChannelDelivery): the conversation continues in the channel with context intact.voice_reply.pyuploads TTS voice replies to the channel (upload_voice_to_channel); markdown deep links are stripped generically before synthesis.
Prompt entities
Section titled “Prompt entities”prompt_providers/ is the prompt-catalog subsystem (bundled use-case prompts
plus user prompts). Use-case prompts include e.g. task-channel-title
(use case channel_title) for naming channel-originated tasks. All bundled
prompts are provider-blind.
Related docs
Section titled “Related docs”- Memory recall/write rules per session mode: knowledge-memory.md
- Channel delivery and thread linking: inbox-channels.md
- The agent/tool layer a turn can reach: overview.md