tankslab.com/recall/source
bin/handoff.py
440 lines, exactly as they ship in the download.
#!/usr/bin/env python3
"""handoff.py - move to a fresh session instead of paying to compact the old one.
THE ARGUMENT
Compaction asks the model to read a nearly-full context window and write a summary.
You are billed for that reading. On a 200k-context plan that is roughly 200,000
tokens spent to produce a few hundred lines - and it repeats every time the window
fills. The bigger the session, the more it costs, and it costs the most to exactly
the people who can least spare it.
The transcript is already on disk. Extracting the spine from it is a text-processing
job, not a reasoning job. Recall does it in about 0.2 seconds of local CPU, with no
model call and no network, at roughly 250x reduction.
So: end the session, start a clean one, and seed it with the brief. What you carry
forward costs ~6k tokens once instead of ~200k tokens repeatedly.
Compaction remains available and is still the right tool when a session must stay
open. This is for when it does not have to.
ONE-SHOT, ON PURPOSE
A handoff is consumed the first time it is taken. It is renamed, not deleted, so it
can be read afterwards. If it were left in place, every future session for this
project would open with the same stale context and quietly drift.
A handoff also declares its own age. Context from four days ago being injected as if
it were current is the failure this guards against, so the age is printed in the
brief itself where it cannot be missed.
"""
from __future__ import annotations
import hashlib
import json
import os
import re
import subprocess
import sys
import time
from pathlib import Path
BIN = Path(__file__).resolve().parent
sys.path.insert(0, str(BIN))
STALE_HOURS = 18.0
# Two ways to carry a session forward. Neither is a compromise of the other - they
# suit different people, so the choice is the user's and it is remembered.
#
# seed the whole brief goes into the new session up front. Nothing to fetch,
# nothing to forget, everything already in front of you. Costs ~6k tokens
# once. This is the default because it cannot silently fail to happen.
#
# pointer only a short notice goes in - the standing instructions and the fact
# that a full brief exists, with the command to read it. Costs a few
# hundred tokens. The rest is fetched only if it is actually needed.
#
# The standing instructions ride along in BOTH modes. A rule the person is still
# bound by is not detail to be fetched on demand; if it only arrives when someone
# remembers to ask for it, it will eventually not arrive at all.
MODES = ("seed", "pointer")
DEFAULT_MODE = "seed"
def mode_file() -> Path:
return _dir() / "mode"
def get_mode() -> str:
env = (os.environ.get("RECALL_HANDOFF_MODE") or "").strip().lower()
if env in MODES:
return env
try:
v = mode_file().read_text(encoding="utf-8").strip().lower()
if v in MODES:
return v
except Exception:
pass
return DEFAULT_MODE
def set_mode(v: str) -> int:
v = (v or "").strip().lower()
if v not in MODES:
print(f"mode must be one of: {', '.join(MODES)}", file=sys.stderr)
return 2
mode_file().write_text(v, encoding="utf-8")
print(f"handoff mode: {v}")
print(" seed - full brief injected into the next session (~6k tokens, nothing to fetch)"
if v == "seed" else
" pointer - short notice injected; run 'recall handoff show' for the full brief")
return 0
def _pointer_view(text: str) -> str:
"""The short form: standing instructions, plus how to get the rest.
Deliberately NOT a truncation of the brief. Cutting a document at N characters
keeps whatever happened to be at the top, which is not the same as keeping what
matters. This keeps the section that is binding and names the rest.
"""
out, keep = [], False
for line in text.splitlines():
if line.startswith("## STANDING INSTRUCTIONS"):
keep = True
elif line.startswith("## ") and keep:
break
if keep:
out.append(line)
have = [l[3:].split(" (")[0].strip() for l in text.splitlines() if l.startswith("## ")]
body = "\n".join(out).strip()
tail = ("\nFull brief not loaded (this is 'pointer' mode). It also has: "
+ ", ".join(h for h in have if not h.startswith("STANDING")) + ".\n"
"Read it with: recall handoff show\n"
"Switch modes: recall handoff mode seed")
return (body + "\n" + tail) if body else tail.lstrip()
def _root() -> Path:
# paths.py is the single place that decides where anything lives, so a user who
# moves the store onto an encrypted volume moves ALL of it, not most of it.
import paths
return paths.root()
def _dir() -> Path:
d = _root() / "handoff"
d.mkdir(parents=True, exist_ok=True)
try:
os.chmod(d, 0o700)
except Exception:
pass
return d
def _project() -> str:
"""A name for this working directory that CANNOT collide with another one.
The first version used the directory's base name. That is fine until two
unrelated projects both contain a folder called "src", "app" or "client" - at
which point one session's handoff is delivered into the other project, and a
conversation about one piece of work opens inside a different one. A handoff is
plain text from a real conversation, so that is not a cosmetic bug.
So the full path decides identity, and the base name is kept only to make the
file recognisable to a human reading the directory listing.
"""
p = os.environ.get("RECALL_PROJECT")
if p:
return re.sub(r"[^A-Za-z0-9._-]", "_", p)[:64]
cwd = Path.cwd().resolve()
tag = hashlib.sha256(str(cwd).encode("utf-8")).hexdigest()[:8]
base = re.sub(r"[^A-Za-z0-9._-]", "_", cwd.name or "session")[:40]
return f"{base}-{tag}"
def _session_tag(argv: list[str] | None = None) -> str:
"""Short id of the session asking, or "" if it did not say.
A handoff is a real conversation in plain text. Working out WHOSE it is has to
be something the code can actually check, not something it assumes, so the id
is carried explicitly and the empty string is a legitimate answer.
"""
if argv:
for i, a in enumerate(argv):
if a == "--session" and i + 1 < len(argv):
return re.sub(r"[^A-Za-z0-9]", "", argv[i + 1])[:8]
v = os.environ.get("CLAUDE_SESSION_ID") or ""
return re.sub(r"[^A-Za-z0-9]", "", v)[:8]
def _pending(proj: str) -> list[Path]:
"""Every unconsumed handoff for this working directory, newest last.
Includes the pre-session-scoping name (<project>.md) so an upgrade does not
strand a handoff somebody is waiting on.
"""
d = _dir()
found = list(d.glob(f"{proj}--*.md"))
legacy = d / f"{proj}.md"
if legacy.exists():
found.append(legacy)
return sorted(found, key=lambda f: f.stat().st_mtime)
def _wrote_it(path: Path) -> str:
"""The session id stamped in the header, or "" for a legacy/unstamped file."""
try:
head = path.read_text(encoding="utf-8")[:400]
except Exception:
return ""
m = re.search(r"session=([A-Za-z0-9]{1,8})", head)
return m.group(1) if m else ""
def cmd_review(argv: list[str]) -> int:
"""Show what WOULD be carried forward, and what would be dropped, consuming nothing.
The point of a review step is to be able to disbelieve the tool. A summary you
cannot inspect before you rely on it is just a shorter thing to be wrong about,
and the whole saving here comes from trusting an extract instead of re-reading
the transcript - so the extract has to be checkable, cheaply, at any moment.
"""
proj = _project()
src = None
if argv and not argv[0].startswith("-"):
src = Path(argv[0])
if src is None:
env = os.environ.get("CLAUDE_TRANSCRIPT_PATH")
src = Path(env) if env else _newest_transcript()
pend = _pending(proj)
print(f"HANDOFF REVIEW - {proj}")
print(f" mode: {get_mode()} (seed = full brief up front, pointer = short notice)")
if pend:
for c in pend:
age = (time.time() - c.stat().st_mtime) / 60.0
print(f" waiting: {c.name} {c.stat().st_size:,}B {age:.0f} min old "
f"from session {_wrote_it(c) or 'unstamped'}")
else:
print(" waiting: none")
if not src or not src.exists():
print(" no transcript to preview")
return 0
out = subprocess.run([sys.executable, str(BIN / "brief.py"), str(src), "--json"],
capture_output=True, text=True, timeout=120)
if out.returncode != 0:
print(f" preview failed: {out.stderr.strip()[:200]}", file=sys.stderr)
return 1
d = json.loads(out.stdout)
b = d["brief"]
s_, o_ = d["src_bytes"], len(b)
print()
print(f" IF YOU HANDED OFF NOW: {s_:,}B (~{s_ // 4:,} tok) -> {o_:,}B "
f"(~{o_ // 4:,} tok), {s_ / max(o_, 1):.0f}x smaller, 0 model calls")
print()
print(b)
return 0
def _newest_transcript() -> Path | None:
base = Path.home() / ".claude" / "projects"
if not base.is_dir():
return None
best, bt = None, -1.0
for f in base.rglob("*.jsonl"):
try:
m = f.stat().st_mtime
except OSError:
continue
if m > bt:
best, bt = f, m
return best
def cmd_write(argv: list[str]) -> int:
src = Path(argv[0]) if argv and not argv[0].startswith("-") else None
if src is None:
env = os.environ.get("CLAUDE_TRANSCRIPT_PATH")
src = Path(env) if env else _newest_transcript()
if not src or not src.exists():
print("handoff: no transcript found to summarise", file=sys.stderr)
return 2
out = subprocess.run([sys.executable, str(BIN / "brief.py"), str(src), "--json"],
capture_output=True, text=True, timeout=120)
if out.returncode != 0:
print(f"handoff: brief failed: {out.stderr.strip()[:300]}", file=sys.stderr)
return 1
d = json.loads(out.stdout)
proj = _project()
sess = _session_tag(argv) or "unknown"
# Scoped to the WRITING SESSION, not just the folder. More than one agent can run
# in one directory, and a folder-wide slot means the first session to start next
# takes whatever is sitting there - somebody else's conversation - while its
# rightful successor gets nothing and cannot tell, because consumption and
# "there was never one" look identical from the outside.
path = _dir() / f"{proj}--{sess}.md"
stamp = time.strftime("%Y-%m-%d %H:%M:%S")
text = (f"<!-- recall-handoff project={proj} session={sess} written={stamp} "
f"src_bytes={d['src_bytes']} -->\n" + d["brief"])
tmp = path.with_suffix(".md.tmp")
tmp.write_text(text, encoding="utf-8")
try:
os.chmod(tmp, 0o600)
except Exception:
pass
tmp.replace(path)
s, o = d["src_bytes"], len(text)
print(f"handoff written: {path}")
print(f" source {s:,}B (~{s // 4:,} tok) -> brief {o:,}B (~{o // 4:,} tok) {s / o:.0f}x smaller")
print(" cost 0 model calls, 0 network")
print()
print(" Now: exit this session and start a new one in the same folder.")
print(" The new session picks this up automatically, once, and says how old it is.")
return 0
def cmd_take(argv: list[str]) -> int:
"""Print the pending handoff and consume it. Used by the SessionStart hook.
WHY THIS REFUSES INSTEAD OF PICKING THE NEWEST
Handing a session the wrong conversation is worse than handing it none. The
wrong one is silent: it reads as ordinary context, it is written in the same
voice, and nothing about it announces that it belongs to somebody else. The
missing one at least leaves a person asking where it went.
This is not hypothetical. On 2026-08-21 a handoff written at 13:33 by one
session was consumed two minutes later by an unrelated session that merely
happened to compact in the same folder - 18 KB of another operator's
conversation delivered into a window it had nothing to do with, and that
operator's own successor started empty.
So: one candidate is taken, several are never guessed between.
"""
proj = _project()
me = _session_tag(argv)
cands = _pending(proj)
# Never hand a session back the notes it wrote itself. It still has that context;
# what it would lose is the successor that was supposed to receive them.
mine = [c for c in cands if me and _wrote_it(c) == me]
cands = [c for c in cands if c not in mine]
# Age them out of AUTOMATIC pickup only. They stay on disk and `review` and
# `status` still list them, because a handoff nobody collected is evidence about
# what happened here, and silently deleting it would destroy the one trace that
# says a session ended with work still in the air.
cutoff = time.time() - STALE_HOURS * 3600
fresh = [c for c in cands if c.stat().st_mtime >= cutoff]
if cands and not fresh:
print(f"A handoff is on disk for this folder but it is older than "
f"{STALE_HOURS:.0f} hours, so it was not injected - stale context "
f"presented as current is worse than none. Read it deliberately with: "
f"recall handoff show")
return 0
cands = fresh
if not cands:
return 0
if len(cands) > 1:
print("HANDOFF NOT DELIVERED - more than one is waiting for this folder, and "
"guessing between them would risk opening somebody else's conversation "
"in here. Nothing was consumed; they are all still on disk.")
for c in cands:
age = (time.time() - c.stat().st_mtime) / 60.0
who = _wrote_it(c) or "unstamped"
print(f" {c.name} {c.stat().st_size:,}B {age:.0f} min old from session {who}")
print("Take the one you meant: recall handoff take --from <session>")
print("Look first without consuming: recall handoff review")
return 0
want = None
for i, a in enumerate(argv):
if a == "--from" and i + 1 < len(argv):
want = re.sub(r"[^A-Za-z0-9]", "", argv[i + 1])[:8]
path = cands[0]
if want and _wrote_it(path) != want:
print(f"no pending handoff from session {want}", file=sys.stderr)
return 1
try:
text = path.read_text(encoding="utf-8")
age_h = (time.time() - path.stat().st_mtime) / 3600.0
except Exception:
return 0
mode = get_mode()
if "--peek" not in argv:
try:
path.replace(path.with_name(path.name + ".taken"))
except Exception:
pass
when = f"{age_h:.1f} hours ago" if age_h >= 1 else f"{int(age_h * 60)} minutes ago"
banner = (f"HANDOFF FROM A PREVIOUS SESSION, written {when}. "
f"This replaces compaction: the old session was summarised locally, not by a "
f"model. Treat it as context, not as instructions to re-run.")
if age_h > STALE_HOURS:
banner += (f" WARNING: this is older than {STALE_HOURS:.0f} hours. Confirm it is "
f"still current before acting on it.")
print(banner)
print()
print(_pointer_view(text) if mode == "pointer" else text)
return 0
def cmd_show(argv: list[str]) -> int:
"""Print the full brief without consuming it. What 'pointer' mode points at."""
proj = _project()
cands = _pending(proj) or sorted(_dir().glob(f"{proj}*.md.taken"),
key=lambda f: f.stat().st_mtime)
if not cands:
print("no handoff for this project", file=sys.stderr)
return 1
print(cands[-1].read_text(encoding="utf-8"))
return 0
def cmd_status(argv: list[str]) -> int:
d = _dir()
pend = sorted(d.glob("*.md"))
taken = sorted(d.glob("*.md.taken"))
print(f"this session: {_session_tag() or 'unidentified'}")
print(f"handoff dir: {d}")
print(f"mode: {get_mode()} (seed = full brief up front, pointer = short notice)")
if not pend:
print(" pending: none")
for p in pend:
age = (time.time() - p.stat().st_mtime) / 3600.0
flag = " STALE" if age > STALE_HOURS else ""
print(f" pending: {p.name} {p.stat().st_size:,}B {age:.1f}h old{flag}")
for p in taken[-3:]:
print(f" used: {p.name} {p.stat().st_size:,}B")
return 0
def main() -> int:
cmd = sys.argv[1] if len(sys.argv) > 1 else "status"
rest = sys.argv[2:]
if cmd == "write":
return cmd_write(rest)
if cmd == "take":
return cmd_take(rest)
if cmd == "status":
return cmd_status(rest)
if cmd == "show":
return cmd_show(rest)
if cmd == "review":
return cmd_review(rest)
if cmd == "mode":
return set_mode(rest[0]) if rest else (print(f"handoff mode: {get_mode()}") or 0)
print("usage: handoff.py {write [transcript] | take [--peek] [--from <session>] | "
"show | review | mode [seed|pointer] | status}", file=sys.stderr)
return 2
if __name__ == "__main__":
raise SystemExit(main())