-
-
Notifications
You must be signed in to change notification settings - Fork 0
Session Management
M31 Autonomous (M31A) persists all session state project-locally, enabling resume, checkpoint, and full conversation history across restarts.
Source: pkg/session/manager.go
Sessions are stored project-locally in <workDir>/.m31a/:
<project>/.m31a/
├── session.json # Session metadata (ID, model, phase, timestamps)
├── session.json.bak # Backup of previous session
├── messages.json # Full conversation history
├── checkpoint.json # Latest workflow checkpoint
├── STATE.md # Current workflow state markdown
├── TASKS.md # Task list with statuses
├── backups/ # Auto-backups from file edits
│ ├── filename.ext.bak.1
│ └── filename.ext.bak.2
└── worktrees/ # Subagent worktree directories
└── agent-<id>/
Global config remains in ~/.m31a/:
~/.m31a/
├── config.toml # User configuration
├── LEDGER.md # Cross-session learning ledger
├── recent_models.json # Recent models + favorites
└── .force-exit # Sentinel for unclean shutdown detection
NewSession(model, provider)
├── Generate random hex ID (crypto/rand)
├── Create <workDir>/.m31a/ directory
├── Backup existing session.json → session.json.bak
├── Write session.json atomically
├── Write empty messages.json
└── Add .m31a/ to .gitignore
LoadSession(id)
├── Read session.json (with 50MB size limit)
├── Validate ID, StartedAt, WorkflowPhase
├── Read messages.json
└── Set ResumedAt timestamp
SaveSession(session)
├── Marshal session + messages
└── Atomic write both files
Source: pkg/session/session.go
type Session struct {
ID string
ParentID string
ChildrenIDs []string
Label string
Tags []string
Model string
Provider string
StartedAt time.Time
ResumedAt *time.Time
MessageCount int
WorkflowPhase WorkflowPhase
Project *ProjectState
Messages []types.Message
Goal string
Questions []string
}Session IDs are cryptographically random hex strings:
- Default: 4 random bytes = 8 hex characters
- Configurable via
features.session_id_length - Generated using
crypto/rand.Read()
All session writes use atomic write semantics:
Source: internal/fileutil/atomic.go
- Write data to a temp file in the same directory
-
fsync()the temp file - Rename temp file to target path
- This prevents corruption on crash or power loss
Session files are capped at MaxSessionFileSize = 50 MB to prevent OOM from corrupted or malicious data:
func readFileLimited(path string, maxBytes int64) ([]byte, error) {
// Stat check + LimitReader double-protection
}Source: pkg/session/checkpoint.go
type Checkpoint struct {
Phase WorkflowPhase
Timestamp time.Time
Metadata map[string]string
}Checkpoints are saved:
- On every phase transition
- Manually via
/saveor/session checkpoint - Automatically during workflow execution
Source: pkg/session/planning.go
The session manager persists workflow state to session.json:
| State | Description |
|---|---|
Goal |
The user's original goal text |
Phase |
Current workflow phase |
Questions |
Discuss phase questions |
Tasks from the Plan phase are saved to TASKS.md and can be loaded back:
func (m *Manager) SaveTasks(sessionID string, tasks []types.Task) error
func (m *Manager) LoadTasks(sessionID string) ([]types.Task, error)Source: pkg/session/session_info.go
type SessionInfo struct {
ID string
Model string
Provider string
StartedAt time.Time
LastModified time.Time
MessageCount int
WorkflowPhase WorkflowPhase
Label string
Corrupted bool
}Used for session listing and display without loading full message history.
When features.resume_on_startup = true, M31A automatically resumes the most recent session:
// In main.go:
if cfg.Features.ResumeOnStartup {
sessions, err := sessionMgr.ListSessions()
if err == nil && len(sessions) > 0 {
app.SetResumeSessionID(sessions[0].ID)
}
}The TUI's Init() method calls loadAndRestoreSession() to restore messages, workflow state, and model selection.
Source: Global config in ~/.m31a/recent_models.json
type RecentModelsData struct {
Recent []string `json:"recent"`
Favorites map[string]bool `json:"favorites"`
}- Tracks up to
maxRecentModels(default 10) recently used models - Supports favoriting models for quick access
- Stored globally (not per-project)
Sessions can be exported in two formats:
| Format | Command | Output |
|---|---|---|
| Markdown | /export markdown [path] |
Human-readable conversation |
| JSON | /export json [path] |
Machine-readable full data |
The session manager automatically adds .m31a/ to .gitignore when creating sessions:
func (m *Manager) ensureGitIgnore() {
// Appends ".m31a/" to .gitignore if not present
}Configurable via features.session_retention_days (default 30). The Cleanup() method is called during Init() but is currently a no-op for project-local sessions (to avoid auto-deleting project data).
Source: pkg/coordinator/coordinator.go
The session coordinator manages concurrent execution of sessions, preventing corruption from parallel access to the same session.
When multiple drains are requested for the same session key, the coordinator coalesces them:
- At most one drain runs per key at a time
- Additional
Run()calls return a context that fires when the current drain completes -
Wake()signals that new work may be available without blocking -
Interrupt()cancels the current drain
The coordinator works with platform-specific file locking (Unix: flock, Windows: direct write) to prevent concurrent writes to session files.
Source: pkg/compaction/compaction.go
When conversations grow large, the session compactor automatically summarizes old messages to prevent context window overflow.
- Token estimation — Uses tiktoken-based estimation with EMA self-calibration
-
Threshold check — Triggers when
estimatedTokens > contextLength - buffer - Message splitting — Divides messages into head (to summarize) and recent (to keep verbatim)
- LLM summarization — Generates a structured summary via the configured model
- Replacement — Replaces old messages with a single compaction summary message
[compaction]
auto = true # Enable automatic compaction
buffer = 20000 # Tokens reserved before compaction triggers
keep_tokens = 8000 # Tokens of recent history to preserve verbatimThe following are preserved in the recent window and never compressed:
- Initial goal message
- All system messages
- Messages with tool calls
- Tool result messages
- Last 8,000 tokens of conversation