astern
crowsnest watches the sessions running right now; astern looks back at the wake they left — the transcripts Claude Code already keeps under ~/.claude/projects/. It mines them for what problems recur, where agents get stuck, what one-off code keeps being rewritten, and which words you and your agents don't share, without spending a token twice.
Start here
pip install astern
astern sync # read new or changed transcripts, run the free (heuristic) lenses
astern sessions # what's synced, newest first
astern show <sid-prefix> # one session: meta, ledger state, last turns, findings
astern report friction # (in progress) a lens's findings as a markdown report
astern judge # (in progress) run the LLM-judged lenses over what's synced
astern estimate # (in progress) token/cost estimate before a judge batch runs
sync, sessions and show are shipped today. report, judge and estimate are being built out by other agents against the same store and ledger; once they land, the one-command loop is astern sync && astern judge && astern report friction.
What it extracts
Each lens asks one question of a session and returns typed, evidence-backed findings — every finding points back at the turn it came from. Method: H = deterministic heuristic, zero tokens; L = LLM-judged, spends subscription tokens via the local claude CLI; H→L = a heuristic shortlist that only the survivors get judged on.
| Lens | Question | Method |
|---|---|---|
stats |
The shape of a session in numbers — the cost model's own features | H |
problems |
What problem was each session solving, and how was it solved? | L |
recurring |
Which problems recur, and would a skill or subagent pay for itself? | H over L |
rewrites |
What one-off code keeps being rewritten? | H→L |
friction |
Where do agents get stuck, and what would remove the obstacle? | H→L |
jargon-in |
Which of my phrasings point to an established term I don't use? | L |
jargon-out |
Which terms do agents use that I don't? | H→L |
corrections |
What did I correct or re-affirm, and which corrections repeat? | H→L |
prompt-quality |
Which prompt shapes lead to clean turns, which to flailing ones? | H→L |
cost |
Where do the tokens and hours go, by project / task / model? | H |
timeline |
What did I work on this week, and what landed? | H→L |
decisions |
Which decisions were made, and why? | L |
claims |
Where did an agent claim "done" and a later turn show otherwise? | H→L |
questions |
What do agents keep asking me, and what did I answer? | H→L |
tooling |
Which skills/subagents/MCP tools are used, unused, or missing? | H |
memory-candidates |
Which restated facts should become memory, and where does memory drift? | H→L |
evals |
Which (prompt, outcome) pairs make a regression test for a skill? | H |
hygiene |
Do agents read before they edit, and does that change over time? | H |
regression |
Did the harness or model change behaviour? | H |
adherence |
Which CLAUDE.md/skill rules are followed, ignored, or fought? | L |
subagents |
Which subagent / Workflow spawns paid for themselves? |
H |
effect |
Did an adopted rule, skill or function reduce the pattern it targeted? | H |
Session handoffs (Q) are deliberately not a lens here — that's openloops, linked rather than re-derived. The full catalogue, with the signal each lens reads and the sink it feeds, is plan §2 (see "The seams" below).
Never spending a token twice
Every lens run goes through the ledger: one entry per session, keyed on the transcript file's fingerprint (size + mtime) and, per lens, its version and the last turn index it has seen. From that, ledger.plan() picks one of three actions:
- skip — this lens version already covered every turn that exists in the transcript.
- incremental — the session grew (it was resumed); only lenses that declare
incremental=Trueget handed just the new turns (from_indexonward) plus their own prior findings, and extend rather than redo. - full — never analyzed, or analyzed by an older lens version; everything is reprocessed.
A heuristic lens (kind='H') costs nothing, so paying for full on every source change is fine. The ledger earns its keep on the LLM lenses (kind='L'): a resumed session must not pay again for turns it already paid for, and astern sync never re-reads a transcript whose size and mtime it already has on file.
Where things live
The store is dol-backed JSON files under ~/.local/share/astern/ (sessions, turns, findings, ledger, judgments) — override with $ASTERN_DATA_DIR to keep an experiment out of the real store. The transcripts it reads come from ~/.claude by default — override with $ASTERN_HOME to point at a second account or a synced copy of another machine's home. Both are seams: any MutableMapping serves as a store, and homes() takes a single dir, an iterable of dirs, or None.
The seams
Five things in astern are deliberately swappable, each with a strong out-of-the-box default and a named replacement:
| Seam | v1 default | Replacement |
|---|---|---|
home= — which ~/.claude-shaped dirs to read |
the one ~/.claude |
a second account, or the server's home over a sync |
store= — where records and findings live |
dol JSON files under ~/.local/share/astern/ |
any MutableMapping — S3, SQLite, a dict for tests |
judge= — the LLM callable for L lenses |
the local claude CLI, headless, no session persisted |
aix.prompt_func for API billing; a recorded-replay judge for tests |
similar= — how findings group across sessions |
normalized-string near match | ir corpus + embeddings, for real semantic clustering |
turns= — the transcript-to-turns fetcher |
astern's own openloops-backed iterator |
priv.claude_transcripts.turn_pair_records by injection |
What's deliberately not a seam: the lens registry (a module-level dict), report templates, the finding dict shape, and the openloops.egress scrub step on every sink — none of these are meant to vary.
Not in astern
- Live sessions — that's
crowsnest's axis (this minute), not astern's (the wake). - Obligation tracking — what's owed, blocked, or open — that's
openloops. - Transcript viewers, full-text search, cost dashboards —
claude-code-log,cc-transcript,episodic-memoryandccusagealready do these well; install them alongside astern rather than rebuilding them here. - Writing into
CLAUDE.md, skills, or memory. Every lens emits candidates — mined skill/rule/function/memory suggestions — for a human to accept throughskill,opsward,coact, orge.memory. Nothing here writes automatically; that's the same "report, never repair" invariantpriv upkeepcarries.
Development
pip install -e ".[dev]"
pytest -q # the test suite
pytest --doctest-modules astern # every module's own doctest
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file astern-0.0.5.tar.gz.
File metadata
- Download URL: astern-0.0.5.tar.gz
- Upload date:
- Size: 72.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
uv/0.12.10 {"installer":{"name":"uv","version":"0.12.10","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8dc659fda5d784e96842ab6e93010ef6e73eb52ed0998691858d1bd03abe98d4
|
|
| MD5 |
b6c60cd3d6ba214905194caedc1d21fc
|
|
| BLAKE2b-256 |
9f8588b8bee4cba791f2e8d18120f275367e97c8a235535bb7bc702c16f15614
|
File details
Details for the file astern-0.0.5-py3-none-any.whl.
File metadata
- Download URL: astern-0.0.5-py3-none-any.whl
- Upload date:
- Size: 62.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
uv/0.12.10 {"installer":{"name":"uv","version":"0.12.10","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3505a12a22d9d69aad0356b0f75da7ec92c276cae90463c06efa7db244bba463
|
|
| MD5 |
007af40d897f02ea850e55b6756cc799
|
|
| BLAKE2b-256 |
88b29f330519489ed5144c9f13795b5a5d31712c1846dd5a6bdf92b5304fd287
|