Version 1.0 — Complete guide to session recording in Harvey
Harvey records every conversation to a
Fountain screenplay file (.spmd
extension), creating a persistent, human-readable, and machine-parseable
record of all interactions. This enables:
| Extension | Description | Usage |
|---|---|---|
.spmd |
Screenplay Markdown — Harvey’s primary session format | Written by Harvey, read by all |
.fountain |
Standard Fountain format | Read by Harvey (for compatibility) |
Note: Harvey writes .spmd files but can
read both .spmd and .fountain files.
# Automatic recording to default location
harvey
# Creates: harvey/sessions/harvey-session-YYYYMMDD-HHMMSS.spmd
# Explicit recording path
harvey --record-file mysession.spmd
# In REPL, check recording status
harvey> /record
Recording: ON
File: /home/user/project/harvey/sessions/harvey-session-20260504-142300.spmd# From command line
harvey --continue harvey/sessions/harvey-session-20260504.spmd
# From REPL
harvey> /session continue harvey/sessions/harvey-session-20260504.spmd
Loaded 15 turns from harvey-session-20260504.spmd
Model: llama3.1:8b (from session recording)# From command line (replay to same file)
harvey --replay oldsession.spmd
# From command line (replay to new file)
harvey --replay oldsession.spmd --replay-output newresponses.spmd
# From REPL
harvey> /session replay oldsession.spmd newresponses.spmd# Sessions are stored in harvey/sessions/ by default
harvey> /sessions
Available sessions (newest first):
[0] harvey-session-20260504-142300.spmd (2026-05-04 14:23:00)
[1] harvey-session-20260503-101500.spmd (2026-05-03 10:15:00)
[2] debugging-issue-42.spmd (2026-05-02 09:30:00)Harvey sessions follow the Fountain screenplay format, a plain-text markup for screenplays that is both human-readable and machine-parseable.
Title: Harvey Session
Credit: Recorded by Harvey
Author: RSDOIEL
Date: 2026-05-04 14:23:00
Draft date: 2026-05-04
FADE IN:
INT. HARVEY AND RSDOIEL TALKING 2026-05-04 14:23:05
Harvey and RSDOIEL are in chat mode. Model: LLAMA3.1. Workspace: /home/user/project.
RSDOIEL
What is the capital of France?
HARVEY
Forwarding to LLAMA3.1.
LLAMA3.1
The capital of France is Paris.
[[stats: LLAMA3.1 · 10 tokens · 0.5s · 20.0 tok/s]]
INT. AGENT MODE 2026-05-04 14:25:00
HARVEY
Harvey proposes to write 1 file(s) to the workspace.
HARVEY
Write notes.md?
RSDOIEL
yes
[[write: notes.md — ok]]
INT. SHELL 2026-05-04 14:30:00
RSDOIEL
! ls -la
SHELL
notes.md
todo.txt
[[shell: ls -la — exit 0]]
THE END.
Harvey uses a multi-character model in Fountain files to distinguish between different participants:
| Character | Representation | Description |
|---|---|---|
USER |
All caps (e.g., RSDOIEL) |
The human operator; name from $USER environment
variable |
HARVEY |
Always HARVEY |
The Harvey agent program |
MODEL |
All caps (e.g., LLAMA3.1, GEMMA4) |
The LLM backend model |
SHELL |
Always SHELL |
Shell command execution output |
SKILL-NAME |
Uppercase skill name (e.g., FOUNTAIN-ANALYSIS) |
Skill execution dialogue |
ROUTE_NAME |
Uppercase route name (e.g., PI2,
CLAUDE) |
Routed endpoint responses |
Character Attribution Rules: -
HARVEY = local Ollama/Llamafile interactions -
ROUTE_NAME = responses from routed endpoints (when
@mention routing is used) - MODEL_NAME =
direct model responses (cloud/direct, or when @modelname is
used) - INT. scenes = Harvey is involved as an
intermediary - EXT. scenes = direct model-human
interaction (not used by Harvey currently)
Harvey sessions contain different scene types that represent distinct interaction modes:
INT. HARVEY AND {USER} TALKING)Standard question-and-answer exchanges between user and model.
Structure:
INT. HARVEY AND {USER} TALKING {TIMESTAMP}
Harvey and {USER} are in chat mode. Model: {MODEL}. Workspace: {PATH}.
{USER}
{user input text}
HARVEY
Forwarding to {MODEL}.
{MODEL}
{LLM response text}
{ROUTE_NAME}
{routed response text}
[[stats: ...]]
[[route: ...]]
Example:
INT. HARVEY AND RSDOIEL TALKING 2026-05-04 14:23:05
Harvey and RSDOIEL are in chat mode. Model: LLAMA3.1. Workspace: /home/user/project.
RSDOIEL
@claude refactor this function
HARVEY
Forwarding to CLAUDE.
CLAUDE
Here is the refactored version of your function...
[[stats: CLAUDE · 250 tokens · 2.5s · 100.0 tok/s]]
INT. AGENT MODE)File operations and tool usage (write, run, edit, etc.).
Structure:
INT. AGENT MODE {TIMESTAMP}
HARVEY
Harvey proposes to write {N} file(s) to the workspace.
HARVEY
Write {filename}?
{USER}
{yes/no/all/quit}
[[write: {filename} — {status}]]
Example:
INT. AGENT MODE 2026-05-04 14:25:00
HARVEY
Harvey proposes to write 2 file(s) to the workspace.
HARVEY
Write src/main.go?
RSDOIEL
yes
[[write: src/main.go — ok]]
HARVEY
Write src/utils.go?
RSDOIEL
all
[[write: src/utils.go — ok]]
INT. SHELL)Shell command execution.
Structure:
INT. SHELL {TIMESTAMP}
{USER}
! {command}
SHELL
{output}
[[shell: {command} — exit {code}]]
Example:
INT. SHELL 2026-05-04 14:30:00
RSDOIEL
! git status
SHELL
On branch main
Your branch is up to date with 'origin/main'.
[[shell: git status — exit 0]]
INT. SKILL {NAME})Skill execution and output.
Structure:
INT. SKILL {SKILL-NAME} {TIMESTAMP}
Harvey executes the {skill-name} skill.
{description}
{SKILL-NAME}
{skill body/instructions}
Example:
INT. SKILL FOUNTAIN-ANALYSIS 2026-05-04 14:35:00
Harvey executes the fountain-analysis skill.
Read and actively monitor a Harvey Fountain screenplay file, parsing its structure...
FOUNTAIN-ANALYSIS
## Analysis Steps
1. Parse the Fountain file...
2. Extract scene metadata...
When @mention routing is used, Harvey records the routing decision and attributes responses to the routed model:
INT. HARVEY AND RSDOIEL TALKING 2026-05-04 14:40:00
Harvey and RSDOIEL are in chat mode. Model: LLAMA3.1. Workspace: /home/user/project.
RSDOIEL
@claude analyze this code
HARVEY
Forwarding to CLAUDE.
CLAUDE
The code has the following structure...
[[stats: CLAUDE · 450 tokens · 3.2s · 140.6 tok/s]]
When a user mentions multiple models or routing is involved:
RSDOIEL
@mistral @claude compare your approaches
HARVEY
Forwarding to MISTRAL.
MISTRAL
My approach uses...
HARVEY
Forwarding to CLAUDE.
CLAUDE
My approach differs by...
Context Window: The last 10 non-system messages are sent to the remote model for context.
| Fountain Element | Syntax | Harvey Usage |
|---|---|---|
| Title Page | Title: value |
Session metadata (Title, Credit, Author, Date) |
| Transition | FADE IN:, THE END. |
Document start/end markers |
| Scene Heading | INT. ... or EXT. ... |
Scene type and timestamp |
| Action | Plain text paragraphs | State description, routing info, stats |
| Character | CHARACTER (all caps) |
Speaker identification (USER, HARVEY, MODEL) |
| Parenthetical | (parenthetical) |
Not currently used by Harvey |
| Dialogue | Text under character | Spoken content (prompts, responses) |
| Note | [[note content]] |
Metadata: stats, file operations, shell results |
| Centered | > centered text < |
Not currently used by Harvey |
Harvey uses Fountain notes ([[...]])
for machine-readable metadata:
| Note Type | Format | Example |
|---|---|---|
| Stats | [[stats: {model} · {tokens} tokens · {time}s · {tok/s} tok/s]] |
[[stats: LLAMA3.1 · 50 tokens · 0.8s · 62.5 tok/s]] |
| Write | [[write: {path} — {status}]] |
[[write: src/main.go — ok]] |
| Shell | [[shell: {command} — exit {code}]] |
[[shell: ls -la — exit 0]] |
| Read | [[read: {path} — {status}]] |
[[read: data.json — ok]] |
| Edit | [[edit: {path} — {status}]] |
[[edit: README.md — ok]] |
| Run | [[run: {command} — {status}]] |
[[run: go test — ok]] |
Sessions are stored in:
<workspace>/harvey/sessions/
Override in harvey.yaml:
sessions_dir: custom/sessions/pathOr via command line:
harvey --sessions-dir custom/sessionsGlobal sessions (not workspace-specific) are stored in:
~/harvey/sessions/
/record — Manage
Recording| Command | Description |
|---|---|
/record |
Show current recording status |
/record on |
Start recording (default at startup) |
/record off |
Stop recording current session |
/record file <path> |
Change recording file path |
Examples:
harvey> /record
Recording: ON
File: /home/user/project/harvey/sessions/harvey-session-20260504-142300.spmd
harvey> /record off
Recording stopped.
harvey> /record on
Recording resumed to /home/user/project/harvey/sessions/harvey-session-20260504-142300.spmd
/session — Session
Operations| Command | Description |
|---|---|
/session continue <file> |
Load chat history from file and continue |
/session replay <file> [output] |
Replay session to current model, optionally saving to new file |
/sessions |
List available session files |
Examples:
# Continue a previous session
harvey> /session continue harvey/sessions/old.spmd
Loaded 23 turns from old.spmd
Resuming conversation...
# Replay to a different model
harvey> /session replay old.spmd new-responses.spmd
Replaying 23 turns from old.spmd
Recording to new-responses.spmd
[1/23] What is the capital of France?
...
# List sessions
harvey> /sessions
[0] harvey-session-20260504-142300.spmd
[1] harvey-session-20260503-101500.spmd
| Flag | Description |
|---|---|
--record |
Enable session recording (default: on) |
--record-file <path> |
Record to specific file |
--no-record |
Disable session recording |
--continue <file> |
Continue from a session file |
--replay <file> |
Replay a session file |
--replay-output <file> |
Output file for replay (use with --replay) |
Examples:
# Start with recording disabled
harvey --no-record
# Record to specific file
harvey --record-file my-session.spmd
# Continue from previous session
harvey --continue ~/harvey/sessions/previous.spmd
# Replay a session with new model
harvey --model claude-3-haiku --replay old-session.spmd --replay-output new-session.spmdHarvey automatically scans for session files on startup:
harvey/sessions/
(newest first)~/harvey/sessions/
(if workspace has no sessions)ListSessionFiles()
Functionfiles, err := ListSessionFiles("harvey/sessions")
for _, f := range files {
fmt.Printf("%s %s\n", f.ModTime.Format("2006-01-02 15:04"), f.Name)
}Returns session files sorted by modification time (newest first).
Harvey provides functions to parse and extract data from session files:
parseFountainSession()Extracts chat turns from a Harvey Fountain file:
userName, modelName, turns, err := parseFountainSession("session.spmd")
for _, turn := range turns {
fmt.Printf("USER: %s\n", turn.UserInput)
fmt.Printf("MODEL: %s\n", turn.ModelReply)
}Returns: - userName — ALL-CAPS user
name from scene headings - modelName — ALL-CAPS model name
from HARVEY’s “Forwarding to” dialogue - turns — Slice of
PlaybackTurn structs with UserInput and
ModelReply
ExtractModelFromSession()Extracts just the model name from a session file:
model, err := ExtractModelFromSession("session.spmd")
// model == "LLAMA3.1"Recorder TypeThe Recorder struct handles writing session events to
Fountain files:
type Recorder struct {
f *os.File // File handle
path string // File path
userName string // ALL-CAPS user name
modelName string // ALL-CAPS model name
workspace string // Workspace path
}r, err := NewRecorder("session.spmd", "Ollama (llama3.1:8b)", "/home/user/project")
defer r.Close()This creates the file, writes the title page, and adds
FADE IN:.
// Simple turn (no stats)
err := r.RecordTurn("Hello", "Hi there!")
// Full turn with stats and routing
stats := ChatStats{
Model: "llama3.1:8b",
Tokens: 50,
Time: 0.8,
TokensPerSec: 62.5,
}
err := r.RecordTurnWithStats(
"Hello",
"Hi there!",
stats,
[]string{"Ollama (llama3.1:8b)"},
"",
)// Start agent mode scene
err := r.StartAgentScene("Harvey proposes to write 1 file.")
// Record write action
err := r.RecordAgentAction("write", "hello.txt", "yes", "ok")err := r.RecordShellCommand("ls -la", "file1.txt\nfile2.txt\n", 0)err := r.RecordSkillLoad(
"fountain-analysis",
"Read and analyze a Fountain session file",
skillBody,
)err := r.Close() // Writes "THE END."When replaying sessions that contain file writes:
{file}.bak.{TIMESTAMP}Example:
[[write: src/main.go — ok]]
# If src/main.go existed, it becomes src/main.go.bak.20260504-142300
[[write: src/main.go — skipped: cannot backup]]
# If backup failed, write is skipped
Every Harvey session file begins with a title page containing metadata:
Title: Harvey Session
Credit: Recorded by Harvey
Author: {USER}
Date: {YYYY-MM-DD HH:MM:SS}
Draft date: {YYYY-MM-DD}
| Field | Source | Format |
|---|---|---|
Title |
Fixed | "Harvey Session" |
Credit |
Fixed | "Recorded by Harvey" |
Author |
Environment | $USER (uppercase) or "OPERATOR" |
Date |
Timestamp | 2006-01-02 15:04:05 |
Draft date |
Timestamp | 2006-01-02 |
For multi-model sessions, a Characters: line can be
added to the title page:
Title: Harvey Session
Credit: Recorded by Harvey
Author: RSDOIEL
Date: 2026-05-04 14:23:00
Draft date: 2026-05-04
Characters: RSDOIEL, HARVEY, LLAMA3.1, CLAUDE
Harvey uses a subset of the Fountain screenplay format:
INT. or
EXT. followed by description and timestamp[[content]] for production
notesFADE IN:,
THE END. for scene markersHarvey session files (.spmd) are valid Fountain files
and can be:
fountain-analysis skill can analyze any Fountain fileHarvey makes these Fountain-specific adaptations:
[[key: value]] format for structured metadataModel:, Workspace: in action descriptionsdebugging-issue-42.spmd instead of
session-1.spmd20260504-research-llm-security.spmdmy-project-analysis.spmd
(avoid spaces)harvey/sessions/project-x/,
harvey/sessions/experiments/harvey/sessions/archive/20260504-llama3.1-research.spmd to track model used20260504-part1.spmd, 20260504-part2.spmdcontinue
to pick up where you left offreplay to compare responses from different models.spmd
files to a safe location@claude, @mistral, etc. for clear
attribution<workspace>/agents/routes.json| Issue | Cause | Solution |
|---|---|---|
| Session not recording | --no-record flag or config |
Check /record status, enable with
/record on |
| Cannot find session file | Wrong path or directory | Use /sessions to list available files, check path |
| Replay produces errors | Model unavailable or rate limited | Check model is running/available, try different model |
| File writes skipped on replay | Backup failed or permission denied | Check file permissions, free disk space |
| Characters not recognized | Non-standard character names | Use ALL-CAPS character names consistently |
If a session file becomes corrupted:
Try to continue anyway: Harvey may still extract valid turns
Check for backup: Harvey doesn’t auto-backup, but you may have manual backups
Reconstruct manually: Create a new session file with the important exchanges
Validate with Fountain parser:
# If fountain CLI is available
fountain validate session.spmdIf a session file is missing the title page: - Harvey will still
parse it but may have incomplete metadata - Author will default to
OPERATOR - Date will be missing from the file
If the model name cannot be extracted: - Defaults to
MODEL - Check that HARVEY has a “Forwarding to {MODEL}.”
dialogue line - Ensure the session was recorded with Harvey (not
manually created)
// Open a session file
user, model, turns, err := parseFountainSession("session.spmd")
// Process turns
for i, turn := range turns {
fmt.Printf("Turn %d:\n", i+1)
fmt.Printf(" User: %s\n", turn.UserInput)
fmt.Printf(" Model: %s\n", turn.ModelReply)
}
// Create a custom session file
r, _ := NewRecorder("custom.spmd", "Custom Model", "/path/to/workspace")
r.StartAgentScene("Custom scene")
r.RecordAgentAction("custom", "target", "yes", "ok")
r.Close()For advanced analysis, use the Fountain library directly:
import "github.com/rsdoiel/fountain"
doc, err := fountain.ParseFile("session.spmd")
for _, elem := range doc.Elements {
switch elem.Type {
case fountain.SceneHeadingType:
fmt.Printf("Scene: %s\n", elem.Content)
case fountain.CharacterType:
fmt.Printf("Character: %s\n", fountain.CharacterName(elem))
case fountain.DialogueType:
fmt.Printf("Dialogue: %s\n", elem.Content)
}
}Convert sessions to other formats:
// Convert to plain text
doc, _ := fountain.ParseFile("session.spmd")
for _, elem := range doc.Elements {
fmt.Println(fountainSrc(&elem))
}
// Convert to JSON
// (Would need custom marshaling of fountain.Element)For complete Fountain syntax, see: - Fountain.io — Official specification - Fountain GitHub — Reference implementation
| Feature | Supported | Notes |
|---|---|---|
| Title Page | ✓ | With Harvey-specific fields |
| Scene Headings | ✓ | INT./EXT. with timestamps |
| Action | ✓ | State descriptions, routing info |
| Character | ✓ | ALL-CAPS only |
| Dialogue | ✓ | Full support |
| Parenthetical | ✓ | Not currently used |
| Notes | ✓ | Structured metadata format |
| Transitions | ✓ | FADE IN:, THE END. |
| Centered | ✗ | Not used |
| Lyrics | ✗ | Not used |
| Page Breaks | ✗ | Not used |
| Section Headings | ✗ | Not used |
Documentation generated from recorder.go, replay.go, and sessions_files.go source code. Version 1.0.