skill
turnal
Local flight recorder for AI coding agents: inspect turns, diff checkpointed workspace state, trace why files changed, and roll back safely.
Set up
Skills are installed by placing their folder where your agent loads skills.
The download comes from github.com/AadiJo/turnal itself, not from our server, so what you get is exactly what its author published.
Install instructions come from the README
README
synced Oct 22, 2026Turnal records what an agent did, the problem it said it was trying to solve, what the workspace looked like before and after each turn, and how to get back safely.
It combines an append-only activity log with private Git checkpoints. Your project history stays local, normal recording never commits to or modifies your existing .git/, and SQLite holds only disposable indexes. The optional workspace-Git rollback mode is the explicit exception: it can restore a previously captured HEAD and index.
Turnal should be piloted before company-wide adoption. Pin a version and validate hook compatibility, retention, and rollback behavior on representative repositories; see the compatibility policy, retention semantics, and recovery runbook.
What you can do
- Inspect agent sessions, prompts, assistant responses, and tool activity.
- Diff the workspace before and after a specific agent turn.
- Attribute current lines to the turns that last changed them.
- Roll the workspace back with a safety checkpoint created first.
- Save an explicit rollback point without committing to the project's Git history.
- Search recorded turns without making SQLite the source of truth.
- Track recorded token usage and API-equivalent cost by turn, session, and project.
- Replay checkpoints in isolated worktrees.
- Run repository-defined checks against the live workspace or a recorded checkpoint.
- Bisect recorded turns to find the one that made those checks fail, with the agent's stated intent.
- Promote recorded turns into immutable Cases, compare isolated Attempts, and apply a selected result.
- Share one Turnal store across linked Git worktrees.
- Publish an explicitly approved, privacy-filtered context history through Git for teammate review.
Requirements
- Git available on
PATH. Turnal uses Git plumbing for its private checkpoint store. - Node.js 18 or newer when installing through npm.
- Go 1.26.5 or newer when installing from source or developing Turnal.
- Claude Code, Codex, Cursor, Pi, OpenCode, or GitHub Copilot CLI for automatic agent capture.
turnal savealso works without an agent session.
Turnal does not initialize a Git repository for your project. It works in both Git and non-Git directories.
Install
With npm:
npm install -g @aadijo/turnal
Install the standalone binaries without Node.js on macOS or Linux:
curl --proto '=https' --tlsv1.2 -fsSL \
https://raw.githubusercontent.com/AadiJo/turnal/main/install.sh | sh
On Windows, run the PowerShell installer:
irm https://raw.githubusercontent.com/AadiJo/turnal/main/install.ps1 | iex
The standalone installers detect x64 or arm64, verify the archive against the SHA-256 checksum published with the same GitHub release, and install turnal plus its external adapter executables. The macOS and Linux default is ~/.local/bin. The Windows default is %LOCALAPPDATA%\Programs\Turnal\bin, which the PowerShell installer adds to the user PATH without elevation.
Each release provides turnal_<version>_<platform>_<architecture>.tar.gz for macOS, Linux, and Windows on x64 and arm64, plus checksums.txt. The checksum detects download corruption; it is not an independent signature if the GitHub release itself is compromised. Select a different directory or pin a version with installer arguments:
curl --proto '=https' --tlsv1.2 -fsSL \
https://raw.githubusercontent.com/AadiJo/turnal/main/install.sh |
sh -s -- --version 0.0.4 --install-dir "$HOME/bin"
& ([scriptblock]::Create((irm https://raw.githubusercontent.com/AadiJo/turnal/main/install.ps1))) `
-Version 0.0.4 `
-InstallDir "$HOME\bin"
The installers never invoke sudo or request elevation; the selected directory must already be writable. Pass -NoPathUpdate to the PowerShell installer to leave the user PATH unchanged.
Turnal is supported on Linux, macOS, and Windows on x64 and arm64. The npm package and standalone release archives ship prebuilt binaries for each. Go is only needed when installing from source or using an unsupported npm platform. Isolated turnal fork execution is available only on those three platforms. On any other target it fails closed with an explicit error instead of running a child without process containment.
From source:
go install github.com/AadiJo/turnal/cmd/turnal@latest
VS Code extension
The first-party VS Code extension is under apps/vscode. It provides current-line AI blame, a native sessions and turns sidebar, read-only turn diffs, and previewed rollback while delegating history and restore operations to the installed turnal CLI. See the extension README for development and local packaging instructions.
Quick start
From the root of the project you want to record:
turnal init --agent all
turnal status
turnal status --probe-agent-capture
turnal init --agent all creates a .turnal/ store, adds .turnal/ to .gitignore, and configures hooks for all supported agents. Initialization does not change your existing .git/.
GitHub Copilot CLI hooks are collaborator-visible files under .github/hooks/. The default --agent auto refreshes an existing .github/hooks/turnal.json but never introduces one merely because GitHub Copilot CLI is installed; use explicit --agent copilot or --agent all after reviewing the repository hook surface.
[!IMPORTANT] Trust the workspace hooks before using your agent. For Codex, launch the Codex CLI in this workspace first and approve the Turnal hooks there before using Codex through another surface, such as the desktop app; those surfaces may not show the hook-trust prompt. For Claude Code, trust the workspace when prompted; no separate hook approval is needed.
When Codex hooks are configured, turnal init prints this trust reminder as a yellow terminal notice. At the end of an interactive initialization, Turnal also offers to install its project-scoped skills; Enter and y accept. The bundled files are stored once under .turnal/skills/, then linked into .agents/skills/ for Codex and .claude/skills/ for Claude Code according to the initialized agents. On Windows, Turnal prefers directory symlinks and falls back to directory junctions when symlink creation requires Developer Mode or elevation.
Normal status is offline. The explicit capture probe starts Codex app-server only long enough to call hooks/list; it does not start a thread or turn, invoke a model, modify workspace files, or change provider trust or configuration. Codex may still update its own local cache and runtime state while app-server starts. The probe also explains the Claude Agent SDK limitation that only the host knows whether it loads project settings.
Agent skills
Turnal includes three project-scoped agent skills. Accept the installation prompt from turnal init to make them available to the initialized agents. Compatible agents can select a skill from its description or invoke it explicitly:
$turnal-inspect-historysearches recorded work when the prompt gives a lead into earlier attempts — "we tried this", "still failing", "why is it like this" — or when you name an inspection command directly. It can recover what the user asked for, what earlier agents attempted, what changed, and whether checks passed.$turnal-fork-historypreviews fork readiness, reruns a recorded task from its historical pre-turn workspace, and compares or selects isolated attempts.$turnal-restore-historyresolves checkpoint arguments, previews rollback, and performs an explicitly requested restore or interrupted-rollback recovery safely.
For example, ask the agent directly:
$turnal-inspect-history Check whether this request has been tried before and recover the earlier user intent before implementing it.
$turnal-fork-history Rerun <session>:<turn> in isolation and compare the result with the existing attempts.
$turnal-restore-history Preview restoring the workspace to before <session>:<turn>; do not apply it yet.
Cursor, Pi, OpenCode, and GitHub Copilot CLI use the versioned external adapter plugin contract. Release packages ship their adapter executables; inspect the contract with turnal adapter contract, verify discovery with turnal adapter list and turnal adapter doctor, then follow the provider hook examples. Cursor subagents and Pi forks retain their parent-session relationship in session listings.
Now use your agent normally. After it has completed a turn:
# Find recorded sessions and turn identifiers.
turnal sessions
# Read recent history or a transcript.
turnal log
turnal log --transcript
# Summarize token usage and estimated API cost.
turnal usage
turnal usage --json
# Inspect and diff one turn.
turnal show <session>:<turn>
turnal diff <session>:<turn>
# Check what Turnal could reconstruct before rerunning a task.
turnal fork <session>:<turn> --dry-run
# Run the captured instruction from the historical pre-turn workspace.
turnal fork <session>:<turn> -- codex exec
# See what would be restored, then perform the rollback.
turnal rollback --to <session>:<turn>:pre --dry-run
turnal rollback --to <session>:<turn>:pre
Rollback targets default to the post-turn checkpoint when the phase is omitted. Use :pre to return to the state before the turn and :post to return to the state after it.
To record the current workspace as an explicit rollback point, use save. The optional message is descriptive metadata; rollback uses the hidden Git commit hash printed by the command.
turnal save "tests passing before refactor"
turnal rollback --to <printed-hash> --dry-run
turnal rollback --to <printed-hash>
Manual saves capture the same project surface as automatic checkpoints. They do not capture the project's Git HEAD or index, so --workspace-git rollback is unavailable for them.
Agent wrapper mode
Turnal can launch every supported agent with wrapper checkpoints in addition to hook capture:
turnal run -- claude -p "inspect this project"
turnal run -- codex
turnal run -- copilot -p "inspect this project"
turnal run -- agent --print "inspect this project"
turnal run -- opencode run "inspect this project"
turnal run -- pi --print "inspect this project"
copilot here is specifically the GitHub Copilot CLI. Turnal installs its repository hooks at .github/hooks/turnal.json and enables repository hooks for the wrapped prompt-mode process unless GITHUB_COPILOT_PROMPT_MODE_REPO_HOOKS is already set. In single-turn prompt mode, Turnal waits for the child to flush its final transcript before settling the provider turn; interactive sessions settle each stop hook immediately so later turns cannot leak into an earlier checkpoint. Review every repository hook before enabling this provider feature.
Wrapper checkpoints remain available when provider hook payloads are unavailable. Prompt, tool, and assistant details still depend on the provider loading the Turnal integration. TURNAL_RUN_ID links provider sessions and attempts to the wrapper run.
An application embedding Codex app-server does not automatically pass through this wrapper. App-server may discover Turnal's project hooks but skip untrusted definitions until you review and trust them in the host's hooks UI; Turnal diagnoses trust but never grants it.
Manual turns
For an unsupported agent or a manual workflow:
turnal turn start --session demo
# Make changes or run the agent.
turnal turn finish --session demo
turnal diff demo:1
Shared history
Shared history is opt-in. It publishes a signed, context-only projection to a Git remote without publishing Turnal snapshots, patches, raw hook payloads, tool inputs, or tool outputs. Preview the exact bundle and approve its policy hash before the first push:
turnal share enable --remote <git-url-or-path> --prompt-mode redacted_text
turnal share preview <session>:<turn> --json
turnal share preview <session>:<turn> --approve
turnal share redaction diagnose
turnal sync push --dry-run
turnal sync push
turnal sync pull
Use turnal share status to inspect consent, pending bundles, blocked projections, and quarantined publishers without contacting the network; add --check-remote for a bounded remote check. turnal share redaction diagnose runs the built-in leak and safe-text corpora and reports the active detector policy. turnal share redaction review <corpus.jsonl>... classifies project examples as false positives or false negatives without echoing their text. When sharing is configured, turnal status also reports a one-line publication summary. Discover local and pulled bundles with turnal share list, which names each source commit and branch and accepts --commit <sha>, and open a locator such as v1:<device-id>:<bundle-id> with turnal share show <locator>. Stop future synchronization with turnal share disable --yes; this preserves existing history and cannot recall published copies. See shared history for the protocol, privacy boundary, and failure semantics.
Teammates joining from independently initialized clones use the publisher's shared repository id: turnal share enable --remote <same-remote> --repo-id <publisher-repo-id> --prompt-mode omit, then turnal sync pull.
How it works
Turnal deliberately keeps four responsibilities separate:
.turnal/log/ append-only, hash-chained agent activity
.turnal/git/ private Git objects and checkpoint refs
.turnal/index/ rebuildable SQLite search and lookup cache
turnal orchestration, validation, and user-facing commands
The event log preserves what the agent explicitly said it was trying to solve. Private Git snapshots preserve what actually changed. Turnal keeps those claims and facts separate, then derives diffs, rollback plans, replay, and line-level blame from both.
Checkpoint refs live under refs/agent-vcs/.... Hidden Git operations use explicit repository paths and scrub inherited GIT_* environment variables so they cannot be redirected into the project’s Git repository.
What gets captured
By default Turnal records:
- User prompts and assistant messages exposed by agent hooks.
- For Claude Code and Codex, compact agent intent statements: the problem, expected scope, and evidence references supplied before an edit.
- Tool names, inputs, and results exposed by agent hooks.
- Before/after workspace snapshots around potentially mutating tool actions when the provider exposes a usable tool boundary. Separate edits in one turn remain distinct; result-only hooks are reconciled with the latest matching call instead of inventing a provider ID.
- Raw adapter payloads, including malformed payloads when they can be preserved.
- Byte-exact contents and executable bits for checkpointed files.
- Symlinks as symlinks, without following their targets.
- User Git context such as branch, HEAD, and dirty state when the workspace is already a Git worktree.
Turnal excludes:
.turnal/and.git/metadata at any depth.- Files ignored by applicable
.gitignorerules. - Paths denied by
secrets.snapshot_deny_globs.
Ignored and secrets-denied files are left untouched during checkpoint rollback. This means the default rollback mode restores the checkpointed project surface, not every byte under the workspace directory.
Privacy and secrets
Turnal stores history locally under .turnal/ and does not upload it during normal recording. The explicit turnal sync push shared-history workflow is the exception. It publishes only the approved projection described above. Prompts and tool payloads can contain credentials or proprietary data, so review both the recording and publication policies before sensitive work.
turnal search --semantic also contacts the network, but only to download its embedding model on first use. Queries and the turns they match are embedded on this machine and are never sent.
Workspace configuration lives at .turnal/config.toml. Global defaults live at the platform-specific user configuration path, normally ~/.config/turnal/config.toml on Linux.
version = 1
[secrets]
store_prompts = false
store_tool_io = false
snapshot_deny_globs = [
".env",
".env.*",
"**/.env",
"**/.env.*",
"**/credentials.*",
"**/*.pem",
]
When prompt or tool storage is disabled, Turnal writes an explicit redaction marker instead of the original content. Agent intent is treated as prompt-like text: store_prompts = false redacts both the durable statement and its turnal intent tool arguments, even when general tool I/O remains enabled. Snapshot deny globs apply to action snapshots and rollback, including rollback from older checkpoints that may contain a now-denied path.
Rollback safety
The default checkpoint rollback:
- Resolves and verifies the target checkpoint.
- Computes a restore plan.
- Creates a private safety snapshot of the current workspace.
- Writes a rollback journal.
- Restores checkpointed files.
- Appends a rollback event to durable history.
Always inspect destructive changes first:
turnal rollback --to <session>:<turn>:pre --dry-run
If a rollback is interrupted, turnal status reports the journal phase. Once a safety checkpoint exists, rollback errors include its ref and commit. Preserve that information, and do not delete .turnal/tmp/rollback-journal.json until you have inspected the workspace and safety checkpoint.
Restoring workspace Git state
Checkpoint rollback intentionally does not move the project’s branch, HEAD, or index. In an existing Git worktree, Turnal can optionally capture and restore that state:
turnal init --git-sync
# Later:
turnal rollback --to <session>:<turn>:pre --workspace-git --dry-run
turnal rollback --to <session>:<turn>:pre --workspace-git
Git-sync capture requires the workspace to already be a valid Git worktree. It is opt-in because it makes rollback materially more invasive.
History and inspection
turnal sessions # Session summaries
turnal sessions --json # Scriptable session output
turnal log # Turn graph for this worktree
turnal log --max-lanes 12 # Override the bounded eight-column graph
turnal log --max-lanes 0 # Allow unlimited graph columns
turnal log --session-limit 10 # Keep the ten most recently active sessions
turnal log --limit 20 # Keep at most twenty turns per session
turnal log --transcript # Prompt/assistant/tool transcript
turnal log --all-worktrees # Shared-store history
turnal show <session>:<turn> # Normalized events for one turn
turnal show <session>:<turn> --full # Include raw records/transcript text
turnal diff <session>:<turn> # Pre-to-post patch
turnal search "authentication failure" # Search the SQLite projection
turnal search "push must fail open" --all-projects
turnal search "why did sync block push" --all-projects --semantic
turnal blame src/auth.go:42 # Agent intent behind a line
turnal blame src/auth.go:42 --verbose # Intent, evidence, human request, and action facts
blame reports the agent's stated problem first and keeps the human request as separate context. Its confidence label is derived from recorded timing and scope: an available statement captured before the action is high confidence, while a late statement or a change outside the stated scope is labeled accordingly. Redacted intent is explicit and remains low confidence because its scope is unavailable. A normal turn with no intent says that none was recorded. When a recorded statement cannot be tied safely to one action, or when turns overlap, Turnal uses explicit ambiguous or concurrent origins instead of borrowing an agent's statement.
The checkpoint graph packs non-overlapping or timestamp-touching session spans into reusable lanes and caps the display at eight columns by default. When more lanes are needed, the most recently active spans keep dedicated lanes and the eighth becomes an overflow lane with turn markers but no connecting line; each inline session label and session-derived true color continues to identify the turn. Graph summaries always include the displayed lane count and disclose overflow or session/turn filtering. Use --verbose to print the full session legend.
If the disposable index is missing or stale:
turnal reindex
Reindexing rebuilds SQLite from the event logs and private checkpoint refs.
Search defaults to the current worktree. --all-worktrees broadens the query
within its Turnal store; --all-projects searches the healthy indexes of every
project registered on this machine and labels each result with its project and
root. A project whose index is missing or stale is reported as a warning
without suppressing results from the healthy ones.
Keyword search remains the offline default. Add --semantic to also match on
meaning, which finds turns that share no words with the query. Keyword hits
always rank above meaning-only hits, and every result states which path found
it. The first semantic search downloads the 8 MB minishlab/potion-base-2M
model from Hugging Face into the user cache; Turnal sends no prompts,
transcripts, tool data, or other recorded history.
Repeated semantic searches reuse embeddings from turnal/embeddings under the
OS user-cache directory. Cache files contain vectors keyed by text hashes, not
raw text. Model or tokenizer changes invalidate them. Deleting this directory
forces recomputation; deleting a project or pruning its history does not remove
these user-cache files. An unavailable or damaged cache falls back to local
inference.
Local viewer
Run turnal ui to open Turnal Prism, a local browser interface for browsing recorded projects, usage, sessions, turns, prompts, tool activity, diffs, and line-level blame. It runs on the loopback interface and can be launched from inside a recorded project or elsewhere to open the project index.
turnal ui
turnal ui --no-open
See the Prism guide for project and session launch options, data limits, and its security model.
Replay
Replay opens history in a separate worktree so inspection does not disturb the active workspace:
turnal replay checkout <session>:<turn>:post
turnal replay show
turnal replay diff
turnal replay prev
turnal replay next
turnal replay stop
Use turnal replay list to find active replay sessions and turnal replay --help for path and retention controls.
Repository verification
Declare an ordered set of verifier commands in the repository's .turnal/config.toml:
[[verify]]
name = "unit-tests"
command = "go"
args = ["test", "./..."]
timeout = "2m"
[[verify]]
name = "race-detector"
command = "go"
args = ["test", "-race", "./..."]
timeout = "5m"
Each command and argument is passed directly to the operating system in declaration order; Turnal does not invoke a shell or expand variables, globs, pipes, or redirects. Names must be unique printable UTF-8 text without formatting controls, commands and timeouts are required, and malformed or excessive definitions make verification fail before any check starts.
Run the checks directly in the current workspace:
turnal verify
turnal verify --json
Live verification does not create a checkpoint or move the project's branch, HEAD, index, or Git configuration. The checks run in the mutable workspace and may modify it themselves, so the report identifies this target as live and does not claim that the result is reproducible.
Run the same declarations against a recorded state:
turnal verify <session>:<turn>:pre
turnal verify <session>:<turn>:post --json
The canonical <session>:turn:<turn>:pre|post spelling is accepted too. Turnal verifies the durable checkpoint metadata and refs before launching a command, then materializes the captured project surface into a Turnal-owned temporary directory. It never runs checkpoint checks in the active workspace and never moves or initializes the project's .git/. The evaluation omits .git/, .turnal/, ignored paths, uncaptured files, and empty directories that were not represented by the checkpoint tree. Turnal reapplies the current secrets.snapshot_deny_globs during verifier materialization, so a newly denied path stays absent even if an older checkpoint captured it; exact replay materialization retains the historical surface.
Checks run sequentially and continue after ordinary failures. Turnal terminates remaining supervised descendants when the direct verifier exits, and a configured timeout terminates the supervised process tree through a Windows Job Object or the child's process group on Unix. If containment cleanup fails, the check keeps its original result while JSON and human output report a separate infrastructure error and the aggregate verification remains unsuccessful. A Unix descendant that deliberately detaches from that process group is outside Turnal's containment boundary, but retained output pipes have a bounded one-second wait so such a descendant cannot keep turnal verify blocked indefinitely. Ctrl+C and SIGTERM cancel the active verifier and trigger owned checkpoint-directory cleanup. Stdout and stderr are captured separately, with the first 1 MiB retained for each stream and independent truncation flags in JSON. Children inherit the caller's environment, toolchain, network access, and current external-service state, but reports record only that inheritance policy and never serialize environment-variable values.
The command exits 0 when every check passes and 3 after running every eligible check when any check fails, times out, or cannot start. Invalid configuration, unresolved or corrupt targets, materialization failures, and cleanup errors are ordinary Turnal errors and exit 1. A passing verifier is evidence for the property that command checks; it is not proof that the entire change is correct.
Standalone verifier reports are printed or returned as JSON. Forked Case attempts also run the verifier contract frozen when the Case was created, and the report is stored with the durable attempt result so later comparisons retain the evidence used at execution time.
Finding the turn that broke a check
turnal bisect binary searches recorded checkpoints with the same verifier declarations to find where the checks go from passing to failing, then names the turn and shows what the agent said it was doing:
turnal bisect
turnal bisect --check unit-tests
turnal bisect --path src/retry.go --session claude-7f2a
turnal bisect --good claude-7f2a:2 --bad claude-7f2a:9 --json
bisect 6 states from claude-7f2a:1:pre to claude-7f2a:5:post, checks: unit-tests
PASS claude-7f2a:1:pre f3f26c8058ae 1.555s
FAIL claude-7f2a:5:post 4e33c3c024f5 119ms
PASS claude-7f2a:2:post b99efaceb7c9 122ms
FAIL claude-7f2a:3:post 24512a52885b 119ms
first bad: claude-7f2a:3:post
last good: claude-7f2a:2:post
culprit: turn claude-7f2a:3
agent: claude
intent: "Add should saturate instead of overflowing" scope add.go
changed: M add.go
failed: unit-tests exit 1
--- FAIL: TestAdd (0.00s)
add_test.go:7: add broken
runs: 4 in 1.918s
The search space is every completed turn in the current worktree, ordered by checkpoint capture time, from the earliest pre-turn checkpoint to the latest post-turn checkpoint. Consecutive checkpoints with identical content count as one state. Both endpoints are verified before the search starts; --good and --bad accept <session>:<turn>[:pre|post] and default to post. Every candidate is materialized into a Turnal-owned temporary directory exactly as turnal verify <target> does, so Turnal itself never modifies the active workspace or the private checkpoint refs. Verifier commands still run with the current machine's toolchain and environment, and ignored paths such as dependency directories are absent from the materialized surface, so a check that needs them fails at the good endpoint too.
The last good and first bad states are always adjacent and both verified. The result then depends on which recorded turns were active between them:
- Exactly one turn, and the first bad state is its post checkpoint: that turn is the culprit, with its recorded prompt, intent statements, changed files, and the failing check's output.
- No turn at all: the change came from
outside recorded turns, and the differing files are listed. - More than one turn, a turn that
--pathor--sessionexcluded from the search, or a turn that never finished: the result isambiguousand lists those turns instead of blaming one of them. This mirrors howturnal blamereports concurrent turns.
--path limits candidates to turns that changed the given workspace-relative file or directory, and --check runs a subset of the configured verifiers, which is useful when only one test is failing. Like git bisect, the search assumes checks stay failing once broken; with a flaky or non-monotonic history it still reports a verified pass-to-fail transition, but not necessarily the earliest one.
The command exits 0 when it identifies a transition, 3 when the good endpoint fails or the bad endpoint passes, and 1 for ordinary errors. A check that cannot start or reports an infrastructure error aborts the search rather than counting as a failing state.
Reproducibility and Cases
Before rerunning a recorded task, inspect what Turnal can reconstruct:
turnal fork <session>:<turn> --dry-run
turnal fork <session>:<turn> --dry-run --json
The report identifies the exact pre-turn checkpoint, captured file count, observable instruction status, provider metadata, and known gaps such as the conversation context that cannot currently be reconstructed, unpinned toolchain, live external services, and secrets that require fresh authorization. A redacted or missing prompt is reported as requiring new user input and is never recovered from raw storage.
Execute a supervised attempt from that checkpoint by placing the command after
--:
# A bare Codex command receives the exact captured instruction automatically.
turnal fork <session>:<turn> -- codex exec
# Any runner can consume the provenance exported as TURNAL_FORK_* variables.
turnal fork <session>:<turn> -- sh -c 'my-runner "$TURNAL_FORK_INSTRUCTION"'
Execution creates or reuses an immutable Case for the source turn, materializes
its pre-turn checkpoint into an owner-only temporary directory, and runs the
child there. The child never runs in the source workspace, .git/ and
.turnal/ are excluded, and inherited GIT_* variables are removed. Turnal
records wrapper pre/post checkpoints, the command status, and the Case's frozen
verifier report as a durable Attempt. The temporary directory is removed by
default; --keep preserves it for inspection. Bare codex and codex exec
commands receive the captured instruction unless --no-replay-instruction is
set; commands with explicit arguments are left unchanged.
Create a Case without running an Attempt when you want to preserve the reproducibility contract first:
# The first Case creates its Task identity.
turnal case create <session>:<turn>
# Inspect the immutable Case or its evolving Task identity.
turnal case show <case-id>
turnal task show <task-id>
# Create a sibling Case when another turn has the same Task instruction.
turnal case create <other-session>:<turn> --task <task-id>
A Case freezes the source turn, Task revision, starting checkpoint, repository
verifier contract, known limitations, and linked Attempts. Creating a sibling
with --task succeeds only when the recorded instruction matches the applicable
Task revision. Once a Case exists, use its ID to avoid ambiguity:
turnal fork <case-id> --dry-run
turnal fork <case-id> -- codex exec
Compare every completed Attempt against the same Case base, record a choice, then preview or apply it:
turnal compare <case-id>
turnal compare <case-id> --patch <attempt-id>
turnal select <case-id> <attempt-id>
turnal apply <case-id> --dry-run
turnal apply <case-id>
Apply is intentionally exact-base only. It refuses to change a workspace whose captured surface differs from the Case base; when the base matches, it uses the journaled rollback engine, creates a safety checkpoint first, restores the selected post-checkpoint, and records the application on the Case. It does not perform a three-way merge.
Worktrees and store history
When initialized inside linked Git worktrees, Turnal can use a shared physical store while preserving an independent worktree identity and event stream.
turnal worktree list
turnal worktree attach --store /path/to/project/.turnal
turnal worktree repair
Immutable history from another Turnal store can be verified and imported:
turnal merge /path/to/source/.turnal --dry-run
turnal merge /path/to/source/.turnal
Turnal does not merge or rewrite the project’s Git history.
Retention and removal
turnal case delete <case-id> --dry-run
turnal case delete <case-id> --yes
turnal session drop <session> --dry-run
turnal session drop <session>
turnal retention prune --dry-run
turnal retention prune
turnal maintenance gc
Cases retain their source and Attempt sessions. Case deletion writes an
irreversible tombstone that makes those sessions eligible for removal; it
requires --yes and refuses while a linked Attempt is still running. Dropping
a session then removes its durable event records and refs. Git objects are
physically reclaimed only after refs are removed and explicit maintenance runs.
To remove Turnal from a workspace:
turnal destroy --dry-run
turnal destroy --remove-hooks
Configuration
Common workspace options:
version = 1
[init]
agent = "auto" # auto | claude | codex | copilot | cursor | opencode | pi | all | none
install_hooks = true
[run]
install_hooks = true
quiet = false
bypass_hook_trust = false
[hooks]
command = "turnal"
[bootstrap]
update_gitignore = true
[git_sync]
enabled = false
[rollback]
mode = "checkpoint" # checkpoint | workspace-git
[[verify]]
name = "unit-tests"
command = "go"
args = ["test", "./..."]
timeout = "2m"
[secrets]
store_prompts = true
store_tool_io = true
snapshot_deny_globs = [
".env",
".env.*",
"**/.env",
"**/.env.*",
"**/credentials.*",
]
Environment variables:
TURNAL_CONFIGselects a global configuration file.TURNAL_HOOK_COMMANDoverrides the command installed into agent hooks.TURNAL_NO_UPDATE_CHECK=1disables interactive update notices.
Troubleshooting
Start with:
turnal status
turnal status --probe-agent-capture
turnal sessions
turnal recovery status
- Hooks need attention: status distinguishes a missing event from an event configured with a different command. Rerun
turnal init --agent <name>forclaude,codex,copilot,cursor,opencode, orpi, or use--agent all, only after reviewing the reported configuration. - Claude Agent SDK is host-controlled: the host must omit
settingSourcesor include"project". Turnal cannot infer arbitrary SDK host configuration and does not consume the SDK stream directly. - Codex app-server hooks are untrusted: review the project and exact hook definitions in Codex's hooks UI. Turnal does not change project trust, hook trust, or private provider trust databases.
- No provider hook payloads: review the provider's project trust and integration status, or use
turnal runfor wrapper checkpoints. - A session is active after an interrupted agent run: resume the same session so the next prompt can close the stale turn, or finalize it manually with
turnal turn finish --session <session>after inspecting the workspace. - Search index missing or stale: run
turnal reindex. - Pending store import: run
turnal merge --recoverorturnal merge --abortas directed bystatus. - Pending rollback journal: preserve the reported safety ref and inspect the workspace before taking further action.
Upgrade
turnal upgrade
turnal upgrade preserves the current release channel. Use turnal upgrade --stable or turnal upgrade --nightly to switch channels explicitly.
The upgrade action follows the installation source:
- npm installations run the matching global npm install.
- Standalone installations download the matching GitHub release archive, verify its checksum and embedded build metadata, and transactionally replace
turnalplus its external adapter executables. - Source or unknown installations print manual update instructions.
Turnal may occasionally print a channel-preserving update notice after interactive commands for npm and standalone installations. Set TURNAL_NO_UPDATE_CHECK=1 to disable these notices.
Development
Install the frontend dependencies, then build the CLI and all bundled adapters with freshly rebuilt viewer assets:
npm ci
npm run build
make build performs the same build. Both commands write the executables to bin/ and require Go, Node.js, and npm.
For Go-only changes, use make build-go or npm run build:go to reuse the checked-in viewer assets. make build-go requires only Go and Make. Both Make build targets accept GO and BIN_DIR overrides, for example make build BIN_DIR=/tmp/turnal-build.
Run the Go checks with:
go test ./...
go vet ./...
The Astro marketing and documentation site is kept outside the npm package:
cd apps/marketing
npm install
npm run dev
Authenticated provider testing is intentionally excluded from the default suite. Set TURNAL_LIVE_PARITY_TEST=1 to run the end-to-end edit contract in trusted disposable repositories for every available provider, or select one with TURNAL_LIVE_PARITY_PROVIDER=claude|codex|copilot|cursor|opencode|pi. The older Codex-only integration remains available with TURNAL_LIVE_CODEX_TEST=1.
Security
Workspace metadata can contain prompts, tool input/output, file history, and raw provider payloads. Review SECURITY.md before using Turnal with sensitive repositories.
License
Turnal's original code and documentation are licensed under Apache-2.0. Third-party names, logos, and trademarks remain the property of their respective owners and are not licensed under Apache-2.0 by this project. See NOTICE for details.