Reasonix
- ID:
reasonix - Store (1.x):
<state>/projects/<workspace-slug>/sessions-v4/<id>/events.framesper workspace,<state>/sessions-v4/<id>/events.framesfor CLI hosts with no workspace, and<state>/desktop-sessions-v5/by-id/<id>/events.framesfor the desktop app - Store (JSONL):
<state>/projects/<workspace-slug>/sessions/<id>.jsonlper workspace, and<state>/sessions/<id>.jsonlfor sessions with none - State root:
$REASONIX_STATE_HOME, else$REASONIX_HOME, else a[storage] stateentry in<home>/config.toml, else~/.reasonixon macOS and Linux and%APPDATA%\reasonixon Windows - Store (legacy):
~/.reasonixon Windows, and the OS config directory (~/Library/Application Support/reasonix,$XDG_CONFIG_HOME/reasonix,~/.config/reasonix) on macOS and Linux — read while they are on disk, unlessREASONIX_HOME,REASONIX_STATE_HOMEorDEJA_REASONIX_ROOTis set - Read overrides:
DEJA_REASONIX_ROOTreplaces the state root; pointed at asessionsdirectory it reads that directory alone - Format: 1.x — an event log of zstd frames; JSONL — one message per line, no envelope
- Needs: the
zstdCLI for 1.x sessions; nothing for JSONL - Resume:
reasonix --resume <id>, run in the workspace the session was worked in; a JSONL session saved with no workspace, or whose workspace is gone, is resumed by its file path from anywhere, and a 1.x session whose workspace is gone is refused withdeja show(#4459). 1.x sessions in the global or desktop store have no resume command
Reasonix is a Go coding agent built around DeepSeek's prefix cache. The
layout above comes from its own resolver, internal/contract/config
(storage_roots.go, paths.go); the workspace slug is the absolute path with
separators and the drive colon turned into dashes, lower-cased on Windows.
A line is {role, content, …} with role one of system, user,
assistant, tool. content is a string. An assistant turn carries
tool_calls: [{id, name, arguments}], where arguments is a JSON string; the
v0.x builds wrote OpenAI's nested {id, type, function: {name, arguments}}
instead, and both are read. bash becomes a command record, read_file,
write_file, edit_file and multi_edit become file records, and the edits
keep the replaced span. notebook_edit, delete_range and delete_symbol
name their file under path and move_file two, under source_path and
destination_path; notebook_edit's new_source is the written side unless
its edit_mode is delete, and delete_range, which names only anchors, has
its removed lines read from the unified diff its result returns — a call that
failed returns an error and gives none (#4541). A tool line is tool output. system lines and user
lines marked host_authored are dropped: the host wrote them, not the person.
Where raw_content is set on a user or tool line it is the text before the
host added context or cut a result for the model, and that is what is indexed.
Beside each transcript sits <id>.jsonl.meta, which carries created_at,
updated_at, workspace_root and the titles (custom_title, topic_title,
name). The session's clock and project come from there, and a rename that
rewrites only this file re-reads the session (#4446).
1.x session directories
Reasonix 1.x (npm reasonix 1.x, branch main-v2) keeps each session as a
directory: manifest.json (sessionId, createdAt, codec
reasonix.session.linear/v4), header.json with the workspace as cwd when
the desktop app made the session, and events.frames. The rest —
events.offset-index.json, storage.identity.json, writer.lock — and the
dot directories beside the sessions (.query-cache, .recovery-cache,
.content-v1) are not transcripts.
events.frames is a run of frames: RX4F, the compressed and the raw size as
big-endian uint32, then one zstd frame holding a JSON record
(internal/session/v4_codec.go). Records come in batches — batch/begin,
the events, batch/end with the SHA-256 of the records before it — and only a
finished batch counts: a trailing batch with no end is a write in progress.
An event's payload is base64 JSON; a payload over 64 KiB is a payloadRef
to .content-v1/objects/<aa>/<bb>/<sha256> beside the sessions.
The message list is replayed the way Reasonix projects it:
message/complete appends {message}, message/upsert replaces by message
id, message/retract drops messageIds, and history/replace and
legacy/import swap in a whole list. session/title is the title. A message
is Reasonix's provider message: origin is user or host, and host
messages (the session-context snapshot) and local_only records are dropped;
createdAt is unix milliseconds on user turns, and other messages take their
batch's time; tool_calls and raw_content read as in the JSONL store, and a
tool result's tool_execution.exitCode rides on its command when non-zero.
The workspace is header.json's cwd, else the Current workspace: "<root>"
line of the host's session-context message (the CLI writes no header), else,
for a desktop session, the workspace that lists its id in
<state>/desktop/workspace-state-v1.json.
Wiring
Reasonix takes everything as one plugin package (reasonix-plugin.json,
apiVersion: reasonix.io/plugin/v2). deja writes the package to
~/.config/deja/reasonix-plugin and hands it to
reasonix plugin install <dir> --name deja --replace --yes, which copies it to
<home>/plugins/deja and records it in <home>/plugin-packages.json. With no
reasonix on PATH deja writes the same layout itself. <home> is
$REASONIX_HOME, else ~/.reasonix on macOS and Linux and
%APPDATA%\reasonix on Windows. config.toml is not touched.
- MCP:
deja install reasonixputs the server in the package's
mcpServers. Package servers start with the session; the model reaches
deja through use_capability.
- Skill:
skills/deja-history/SKILL.mdin the package. - Command:
commands/deja.mdin the package, listed as/deja:deja. - Auto-recall:
deja install reasonix-autoadds a runtime, `deja
reasonix-ext`, speaking Reasonix's extension protocol v2 (JSON-RPC 2.0 over
stdio). Hooks cannot carry it: on 1.39.1 only SessionStart stdout reaches
the model. The extension appends the session digest (first turn) and the
per-prompt recall to the user turn at input.receive, so recall sits in the
turn tail and the prefix cache is not disturbed; the system prompt is never
edited. Recall is asked about what the person typed: the host's own blocks,
the plan-mode marker and Referenced context: file bodies are left out of
the query, and the host's own turns (a goal round, the message after a plan
approval) get nothing. Reasonix stores the typed text as raw_content, and
that is what deja indexes. At tool.after it adds the pre-tool line for a
shell command (bash, or pwsh and powershell on Windows) or a file
write — write_file, edit_file, multi_edit, notebook_edit,
delete_range, delete_symbol, and move_file under the file it moved
from (#4541) — and when a command failed, what fixed the same failure before. Both
go after the output; for output Reasonix will cut to a CI summary (first
and last eight lines) they go in front, on one line. At
compaction.prepare it adds deja's record of the folded turns, read from
what the person typed rather than the recall appended to it, to the
summarizer's guidance. It publishes a status line while the first index
builds and one "recalled N prior sessions" notice per session. Every answer
has a budget under the host's timeout (4 s per prompt, 2 s per tool call,
10 s for compaction); an error, a timeout or nothing to say leaves the turn
as the host built it.
- Statusline: not installed. A Reasonix statusline replaces the built-in
row and lives in config.toml; the extension's status and notice lines
carry the same information.
- Trust: a runtime runs with full trust.
--yesis the approval, and
install prints what was trusted; reasonix plugin show deja lists the
intercepts.
- Uninstall: removes the package directory and deja's record, and the
state file too when deja created it. A deja package deja did not write —
a linked one (plugin install --link), or a record pointing at another
source — is left alone, and install refuses to replace it. When install
ran reasonix, the receipt-signing key and crash directory that run
created go too, unless Reasonix has used the key since.
- Resume:
reasonix --resume <id>in the session's workspace. - Handoff: paste.
Last verified: 2026-09-27
Known quirks and drift
- Per-message time is partial. The host stamps
createdAt(unix
milliseconds) on the turns it records; tool results carry none. An unstamped
line sits one millisecond after the line before it, so two identical turns
stay two records (#3333).
- v0.x kept the clock elsewhere. Its
<id>.meta.jsonholds only
workspace and summary, and the timestamps are ts in
<id>.events.jsonl. With neither sidecar present the file's mtime is used.
- Legacy copies. Reasonix imports legacy sessions into its current store
and leaves the originals, so a legacy transcript is read only when the
current store has no file with its name.
- Copies between stores. 1.x mirrors a JSONL transcript into
sessions-v4 under the same id, migrates one under a new id naming the
original in the manifest's source.path, and the desktop imports sessions
into its own store, recording each source under sourceMappings in
workspace-state-v1.json. None of them deletes the original. deja reads the
newest store's copy: desktop, then sessions-v4, then JSONL. A fork also
names its parent in source.path; that is a directory, and the parent is
still read.
- Resume in 1.x.
--resume <id>looks in thesessions-v4store whose
slug is the working directory's, not the git root the session-context names,
so the directory deja prints is the one whose slug matches the store.
The global and desktop stores are not searched by --resume.
- Sidecars share the directory.
.events.jsonl,.wire.jsonl,
.guardian.jsonl, .conflicts.jsonl, .adjudication.jsonl,
.execution.jsonl and .turns.jsonl end in .jsonl and are not
transcripts; nor are flat subagent-*.jsonl worker logs or the subagents/
tree. Locks, metadata, context and recovery files sit there too.
- Where the shapes come from. The JSONL store is read from Reasonix's own
code (internal/state/store/session.go, internal/state/sessionstore,
internal/contract/provider/provider.go) and from the transcript sample in
4053, not from a running install. The 1.x store is read from the 1.x code
(internal/session, internal/sessioncontent, internal/config/paths.go)
and was checked against a
live reasonix 1.39.1: sessions it wrote are read here, and it resumes the
1.x fixture deja checks in.
- Pre-tool lines arrive with the result. A
tool.beforeanswer can
only let a call run, change it or stop it, so the line deja has about a
command or file is asked for at tool.after, which fires only for a call
that ran, and added to that call's result.
- Which session. The extension is told a host-local id (
boot-1on
1.39.1). A session.start or session.load event names the session in
sessionPath where Reasonix has one — a bare id on the 1.x binding, a
session directory or a JSONL path elsewhere — and that is the key; a
session.rotate names the session that is ending. Without a name, deja
takes the one sessions-v4 directory in the workspace whose manifest
createdAt is after the session began and that no earlier session in the
process was filed under; with none or several it files the session under a
key of its own. A session event with no name that arrives within two
seconds of a turn is taken as that turn's own.
- 2.x is checked from source only. Reasonix 2.x (branch
studio) has
the same v2 manifest and extension protocol, so the package is the same
there; it has not been run against a 2.x build.
deja reads this format and thirty-seven others, and turns what it finds into memory your agents can search. See the harness matrix for what is wired where, or install it and search your own history.
Found this useful? Star deja-vu on GitHub.