Sessions
Every Nexus run creates a session — a persistent workspace on disk that captures conversation history, thinking steps, plans, and plugin data.
Directory Structure
Sessions are stored under the configured root directory (default: ~/.nexus/sessions/):
~/.nexus/sessions/<session-id>/
├── context/
│ └── conversation.jsonl # Conversation history (from memory plugin)
├── files/ # Files created during the session
├── journal/
│ ├── active.jsonl # Live event journal (every bus event,
│ │ # including thinking.step + plan.progress)
│ └── *.jsonl.zst # Rotated, zstd-compressed segments
├── metadata/
│ ├── session.json # Session metadata
│ └── config-snapshot.yaml # Config used for this session
└── plugins/
└── <plugin-id>/ # Per-plugin data directories
Thinking steps and plan progress are no longer kept in dedicated
thinking.jsonl / plans.jsonl files — they live in the journal
alongside every other event. Read them via
journal.Writer.SubscribeProjection (live) or journal.ProjectFile
(post-mortem).
The journal records every bus event except the types listed in
journal.exclude_events (default ["core.tick"]). Excluded events
still dispatch to bus subscribers — only the durable log skips them,
and their seq is not consumed, so on-disk envelopes stay gap-free.
The default suppresses the engine heartbeat, which replay regenerates
from the live tick goroutine and which otel / eval already treat as
noise. See configuration reference
for the full key.
Session Metadata
Each session tracks metadata in metadata/session.json:
type SessionMeta struct {
ID string // Random hex identifier
StartedAt time.Time // When the session began
EndedAt *time.Time // When the session ended (nil if active)
Profile string // Config profile name
Plugins []string // Active plugin IDs
Labels map[string]string // User-defined labels
TurnCount int // Number of conversation turns
TokensUsed int // Total tokens consumed
PromptTokensUsed int // Input tokens consumed
CompletionTokensUsed int // Output tokens consumed
CostUSD float64 // Accumulated cost in USD
Status string // "active" or "ended"
}
Session Workspace API
Plugins interact with the session through the SessionWorkspace struct:
// Write a file to the session workspace
session.WriteFile("context/mydata.json", data)
// Read a file back
data, err := session.ReadFile("context/mydata.json")
// Append to a file (useful for JSONL logs)
session.AppendFile("context/events.jsonl", line)
// List files in a subdirectory
files, err := session.ListFiles("context")
// Check if a file exists
exists := session.FileExists("context/conversation.jsonl")
Directory Helpers
session.ContextDir() // ~/.nexus/sessions/<id>/context/
session.FilesDir() // ~/.nexus/sessions/<id>/files/
session.MetadataDir() // ~/.nexus/sessions/<id>/metadata/
session.PluginDir("nexus.tool.shell") // ~/.nexus/sessions/<id>/plugins/nexus.tool.shell/
PluginDir() creates the directory lazily on first access.
File Events
When files are written to the session, events are emitted automatically:
| Event | When |
|---|---|
session.file.created | A new file is written |
session.file.updated | An existing file is overwritten |
These events carry the file path, session ID, and file size. The TUI plugin subscribes to these to show file creation notifications.
Session Lifecycle
Creating a Session
When Engine.Run() starts, it calls NewSessionWorkspace() which:
- Generates a random hex session ID
- Creates the directory structure (
context/,files/,metadata/,plugins/) - Writes initial metadata with status
"active"
Resuming a Session
When launched with -recall <sessionID>:
- The engine loads the session’s config snapshot from
metadata/config-snapshot.yaml LoadSessionWorkspace()opens the existing directory- The session metadata is updated back to
"active" - Plugins find their persisted data in their
PluginDir()
Ending a Session
On shutdown, the engine:
- Sets
EndedAton the session metadata - Updates status to
"ended" - Saves a config snapshot for future recall
The snapshot is the original config YAML bytes verbatim, not a re-serialization
of the typed Config struct. core.models and per-plugin configs are parsed
via a second-pass raw map (yaml:"-" on the typed fields), so re-marshaling
would silently drop them and break recall. Configs constructed in-memory via
DefaultConfig() (no source bytes) fall back to yaml.Marshal of the typed
struct.
Configuration
Session behavior is configured in the core.sessions section:
core:
sessions:
root: ~/.nexus/sessions # Where sessions are stored
retention: 30d # How long to keep old sessions
id_format: datetime_short # ID generation format
| Field | Default | Description |
|---|---|---|
root | ~/.nexus/sessions | Base directory for all sessions |
retention | 30d | Retention period for old sessions |
id_format | timestamp | Format for generating session IDs |