Save
Checkpoint. Route the stash. Update state. Generate projections. Keep working.
Save is NOT a termination. The session continues. Save can happen multiple times. Each save increments the saves: counter and updates last_saved:. The stop hook only blocks when saves: 0 (never saved).
Flow
1. Read First (understand before acting)
Read these in parallel before presenting the stash or writing anything:
_kernel/now.json— which bundle is active? What was the context? Which tasks are urgent or active?_kernel/log.md— first ~100 lines (recent entries — what have previous sessions covered?)- Active bundle's
context.manifest.yaml— ifnow.jsonreports an active bundle, read its manifest
Do NOT read per-bundle task files directly — task data lives in now.json already (computed projection), or call tasks.py list --walnut {path} if you need specific detail. In v3, tasks are stored in tasks.json per walnut and per bundle, managed only through the supported task tooling (alive tasks promote for batched stash promotion in step 6c, scripts/tasks.py for direct edits like done / priority changes). Never edit tasks.json from the agent.
If _kernel/now.json does not exist: suggest running python3 "$ALIVE_PLUGIN_ROOT/scripts/project.py" --walnut {path} to generate it.
Standalone session (no walnut loaded): If no walnut was opened this session, the squirrel still has a stash to route. Ask: "Which walnut does this session belong to?" If the human names one, load its core files and proceed normally. If the human cannot name one, abort the save — on the fn-12 CLI-only save path, alive log prepend requires a concrete walnut (it only targets {walnut}/_kernel/log.md). Surface a bordered block explaining the save is blocked until a walnut is chosen; the stash stays in conversation and the squirrel YAML at .alive/_squirrels/ keeps walnut: null until the next save. Do NOT attempt to write to a world-level .alive/log.md — the CLI does not support that path.
This gives the squirrel the full picture BEFORE it starts routing. It knows which bundle was active, what previous sessions accomplished, and what the task state is. This makes everything that follows smarter — better routing suggestions, better log entries that don't duplicate what's already recorded.
2. Pre-Save Scan
"Anything else before I save?"
Then scan back through messages since last save for stash items the squirrel may have missed. Add them.
3. Confirm Stash (batched)
Present the full stash visually in a single bordered block for readability, then batch confirmations into as few AskUserQuestion calls as possible.
Display:
╭─ 🐿️ save checkpoint
│
│ decisions (3)
│ 1. Orbital test window confirmed for March 4 → nova-station
│ 2. Ryn's team handles all telemetry review → nova-station
│ 3. Festival submission over gallery showing → glass-cathedral
│
│ tasks (2)
│ 4. Book ground control sim for Feb 28 → nova-station
│ 5. Submit festival application by Mar 1 → glass-cathedral
│
│ notes (1)
│ 6. Jax mentioned new radiation shielding vendor → [[jax-stellara]]
╰─
Then one AskUserQuestion call with up to 3 questions — skip empty categories:
| Question slot | Category | Options |
|---|---|---|
| 1 | Decisions | "Confirm all" / "Review list" / "Drop some" |
| 2 | Tasks | "Confirm all" / "Edit or drop" |
| 3 | Notes | "Confirm all" / "Drop some" |
You can select an option OR use "Other" to provide free text — editing items, adding context, changing routing, or explaining what happened. Every question supports elaboration.
Insight candidates get a separate call (if any exist) because they require a different decision — commit as evergreen vs just log it:
╭─ 🐿️ insight candidate
│ "Orbital test windows only available Tue-Thu due to
│ ISS scheduling conflicts"
│
│ Commit as evergreen insight, or just log it?
╰─
→ AskUserQuestion: "Commit as evergreen" / "Just log it"
4. Write Log Entry
Before writing anything else, prepend a signed entry to _kernel/log.md. This is the primary record of what happened. The log entry uses the standard template:
- What happened (brief narrative)
- Decisions made (with rationale — WHY, not just WHAT)
- Tasks created or completed
- References captured
The log entry must be written BEFORE any other files. The log is truth. Everything else derives from it.
Write the entry via the alive log prepend CLI, not via the Edit tool. The CLI owns the deterministic parts of the entry block (heading, entry-hash marker, signed line, separator) and the frontmatter bump (entry-count, last-entry, summary). Your responsibility is the body prose + summary string.
Step 4a — Precheck (before composing anything):
Run the doctor log check. If it fails, abort the save, surface the hint to the human, and stop — do not attempt to write the log entry any other way.
"$ALIVE_PLUGIN_ROOT/bin/alive" doctor --check=log --walnut {walnut-path}
Python fallback if bin/alive is missing:
"${ALIVE_PYTHON:-python3}" "$ALIVE_PLUGIN_ROOT/scripts/cli.py" doctor --check=log --walnut {walnut-path}
Parse the JSON stdout. If check.status != "ok", show the check.hint verbatim to the human inside a bordered block and stop. Do NOT fall back to Edit-tool log prepend. The CLI is the only supported write path on this version of the plugin.
Step 4b — Compose body + summary in memory.
The body is pure prose — the CLI adds the heading, hash marker, signed line, and separator. Do NOT write ## <date> -- squirrel:..., <!-- entry-hash: ... -->, signed: squirrel:..., or a trailing --- yourself; those come from the CLI.
The summary is a one-line distillation of the entry (what changed, not the full narrative). It lands in the log frontmatter's summary: key.
Step 4c — Invoke the CLI.
Single-line summary — pass via --summary CLI arg, body via stdin heredoc:
"$ALIVE_PLUGIN_ROOT/bin/alive" log prepend \
--walnut {walnut-path} \
--entry-file - \
--summary "{escaped-single-line-summary}" <<'ALIVE_ENTRY_BODY'
{body prose, any markdown, any number of lines}
ALIVE_ENTRY_BODY
Multi-line summary — write the summary to a real temp file and pass --summary-file. Never use process substitution <(...) — it's shell-fragile in agent-invoked contexts and silently misbehaves under some Bash tool wrappers. Use trap ... EXIT so the temp file is removed even when the CLI exits non-zero and the skill aborts the save:
Compose the temp-file path with mktemp first, then write the summary to it via the agent's native file-write tool (NOT a shell heredoc — heredocs append a trailing newline, and --summary-file is read verbatim with no rstrip, so a stray \n would land in the frontmatter summary:). Use trap ... EXIT so the file is removed even when the CLI exits non-zero and the skill aborts the save:
SUMMARY_FILE=$(mktemp -t alive-summary.XXXXXX)
trap 'rm -f "$SUMMARY_FILE"' EXIT
# Now write the exact multi-line summary text to $SUMMARY_FILE
# via the agent's native file-write tool. Do NOT use a bash
# heredoc (trailing newline) or printf with per-line shell
# quoting (breaks on apostrophes inside the summary).
#
# Then invoke the CLI:
"$ALIVE_PLUGIN_ROOT/bin/alive" log prepend \
--walnut {walnut-path} \
--entry-file - \
--summary-file "$SUMMARY_FILE" <<'ALIVE_ENTRY_BODY'
{body prose}
ALIVE_ENTRY_BODY
rm -f "$SUMMARY_FILE"
trap - EXIT
Python fallback — single-line summary (same arg shape; substitute the binary):
"${ALIVE_PYTHON:-python3}" "$ALIVE_PLUGIN_ROOT/scripts/cli.py" log prepend \
--walnut {walnut-path} --entry-file - --summary "{escaped}" <<'ALIVE_ENTRY_BODY'
{body}
ALIVE_ENTRY_BODY
Python fallback — multi-line summary (same temp-file + trap lifecycle as the primary path):
SUMMARY_FILE=$(mktemp -t alive-summary.XXXXXX)
trap 'rm -f "$SUM