{"id":51334219,"url":"https://github.com/varmabudharaju/tend","last_synced_at":"2026-07-02T01:05:53.924Z","repository":{"id":363953315,"uuid":"1265710930","full_name":"varmabudharaju/tend","owner":"varmabudharaju","description":"Context hygiene for Claude Code — fail-open hooks that file away oversized outputs, externalize session state to STATE.md, pin the goal to every prompt, and turn compaction into a curated, well-timed event. Stays sharp deep into long sessions.","archived":false,"fork":false,"pushed_at":"2026-06-11T03:46:14.000Z","size":330,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2026-06-11T04:21:31.913Z","etag":null,"topics":["ai-agents","claude","claude-code","context-window","developer-tools","hooks","llm"],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/varmabudharaju.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-11T02:43:00.000Z","updated_at":"2026-06-11T03:46:17.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/varmabudharaju/tend","commit_stats":null,"previous_names":["varmabudharaju/tend"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/varmabudharaju/tend","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/varmabudharaju%2Ftend","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/varmabudharaju%2Ftend/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/varmabudharaju%2Ftend/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/varmabudharaju%2Ftend/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/varmabudharaju","download_url":"https://codeload.github.com/varmabudharaju/tend/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/varmabudharaju%2Ftend/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35028671,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-01T02:00:05.325Z","response_time":130,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ai-agents","claude","claude-code","context-window","developer-tools","hooks","llm"],"created_at":"2026-07-02T01:05:52.964Z","updated_at":"2026-07-02T01:05:53.913Z","avatar_url":"https://github.com/varmabudharaju.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# tend\n\n[![tests](https://github.com/varmabudharaju/tend/actions/workflows/ci.yml/badge.svg)](https://github.com/varmabudharaju/tend/actions/workflows/ci.yml)\n[![license: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)\n[![python 3.11+](https://img.shields.io/badge/python-3.11+-blue.svg)](pyproject.toml)\n\n**Keep Claude Code sharp in long sessions.**\n\ntend is a small, invisible helper that rides along with [Claude Code](https://claude.com/claude-code) and keeps its limited \"working memory\" clean — so the assistant stays smart ten hours into a big task instead of slowly losing the plot.\n\n```bash\npip install -e . \u0026\u0026 tend install-hook    # 30 seconds, fully reversible, no daemon\n```\n\n![what happens to a long session, with and without tend](docs/screenshots/the-problem.gif)\n\n```mermaid\nflowchart LR\n    S[\"Your Claude Code session\"] --\u003e|every event| T{\"tend\u003cbr/\u003e(8 tiny hooks)\"}\n    T --\u003e O[\"Files away huge outputs\u003cbr/\u003eto disk\"]\n    T --\u003e N[\"Maintains a shared notebook\u003cbr/\u003eSTATE.md\"]\n    T --\u003e A[\"Pins the goal + health\u003cbr/\u003eto every prompt\"]\n    T --\u003e C[\"Times memory cleanup\u003cbr/\u003eso nothing is lost\"]\n```\n\n## The problem, in plain words\n\nAn AI assistant has a fixed amount of working memory, called the *context window*. Think of it as a desk. Every file it reads, every command output, every conversation turn is another sheet of paper on that desk.\n\nOn long tasks the desk fills up with old papers: a 5,000-line log it needed once, three versions of a file it already fixed, dead-end experiments. Two things go wrong:\n\n1. **The assistant gets dumber.** The important stuff (what are we building? what did we decide?) is buried under junk.\n2. **Eventually the desk overflows.** The assistant has to sweep papers into a box (\"compaction\") — and without supervision it sometimes sweeps away the notes that mattered.\n\ntend is the colleague who quietly keeps the desk tidy.\n\n```mermaid\nflowchart TB\n    subgraph without [\"Without tend\"]\n        direction TB\n        a1[\"Desk fills with old papers\"] --\u003e a2[\"Important notes get buried\"]\n        a2 --\u003e a3[\"Desk overflows\"]\n        a3 --\u003e a4[\"Cleanup sweeps away\u003cbr/\u003edecisions and lessons learned\"]\n        a4 --\u003e a5[\"Assistant repeats old mistakes\"]\n    end\n    subgraph withtend [\"With tend\"]\n        direction TB\n        b1[\"Big papers filed in drawers\"] --\u003e b2[\"Goal pinned on top,\u003cbr/\u003ealways visible\"]\n        b2 --\u003e b3[\"Cleanup happens between tasks,\u003cbr/\u003enever mid-thought\"]\n        b3 --\u003e b4[\"Notebook survives everything\"]\n        b4 --\u003e b5[\"Assistant stays sharp\"]\n    end\n```\n\n## What tend does — four habits\n\n| Habit | In plain words | In Claude Code terms |\n|---|---|---|\n| **File it, don't pile it** | A 10-page printout gets filed in a drawer; a sticky note on the desk says which drawer. | Oversized tool outputs are replaced with a head+tail excerpt; the full text is saved to disk with its path, retrievable with a bounded `Read`. |\n| **Keep a notebook** | Goals, decisions, and dead-ends live in a notebook that survives even if the desk is cleared. | Each project gets `.claude/tend/STATE.md` (Goal / Now / Decisions / Dead-ends). New sessions auto-load it, so `/clear` is a lossless handoff. |\n| **Sticky note with the goal** | A note on the monitor: *what we're doing, how full the desk is*. | A ≤400-token anchor is injected with every prompt: goal, current step, context %, stale-result warnings, and a `/compact` recommendation when it's time. |\n| **Clean up at the right moment** | Tidy between tasks — never mid-thought, never throwing out the notebook. | tend detects task boundaries, recommends *curated* compaction (keep goal/decisions, drop raw outputs), snapshots before every compaction, and blocks one stale auto-compact until the notebook is updated. |\n\nAnd one bonus habit, added after watching real usage bills:\n\n| Habit | In plain words | In Claude Code terms |\n|---|---|---|\n| **Right-sized helpers** | When the assistant hires a helper, a note suggests: this errand doesn't need the most expensive expert. | When a subagent is spawned without an explicit model, tend suggests the cheapest model tier that fits the job (see [swarm](https://github.com/varmabudharaju/swarm) for the full tiering system). |\n\nThe first habit, in one picture:\n\n```mermaid\nflowchart LR\n    big[\"A tool returns 20,000 tokens\u003cbr/\u003eof build logs\"] --\u003e tend{\"tend\"}\n    tend --\u003e|\"stays in working memory\"| ex[\"First lines + last lines\u003cbr/\u003e+ a note naming the drawer\"]\n    tend --\u003e|\"goes to disk\"| file[\"The full output, saved.\u003cbr/\u003eAny slice readable later.\"]\n```\n\nAnd the notebook habit — why nothing important ever dies:\n\n```mermaid\nflowchart LR\n    s1[\"Session 1 works,\u003cbr/\u003ewriting the notebook as it goes\"] --\u003e n[\"STATE.md\u003cbr/\u003eGoal / Now / Decisions / Dead-ends\"]\n    n --\u003e e{\"Session ends:\u003cbr/\u003ecrash, /clear, or just Friday\"}\n    e --\u003e s2[\"Session 2 auto-loads the notebook\u003cbr/\u003eand picks up where 1 left off\"]\n```\n\n## See it\n\ntend speaks to the model's context, not your chat — the conversation stays clean, while the transcript view (Ctrl+O) shows every injection verbatim. What's below is the rest of its visible surface. A 15-second tour, all real tool output:\n\n![tend in action: status dashboard, automatic offloading, lossless handoff](docs/screenshots/demo.gif)\n\n| `tend status` | `tend report` | `tend handoff` |\n|---|---|---|\n| ![tend status](docs/screenshots/01-tend-status.png) | ![tend report](docs/screenshots/02-tend-report.png) | ![tend handoff](docs/screenshots/03-tend-handoff.png) |\n\n## How it fits into a session\n\n```mermaid\nsequenceDiagram\n    participant U as You\n    participant CC as Claude Code\n    participant T as tend\n\n    U-\u003e\u003eCC: new session\n    T--\u003e\u003eCC: restore STATE.md (lossless handoff)\n    U-\u003e\u003eCC: prompt\n    T--\u003e\u003eCC: anchor: goal, context health\n    CC-\u003e\u003eCC: runs a tool, output is huge\n    T--\u003e\u003eCC: excerpt in context, full text filed on disk\n    CC-\u003e\u003eCC: context fills up\n    T--\u003e\u003eCC: \"task boundary + 60% full: good moment for a curated /compact\"\n    CC-\u003e\u003eCC: compaction (tend snapshots first)\n    Note over T: the notebook and the filed outputs survive\n```\n\n## Install\n\n**As a Claude Code plugin** (recommended):\n\n```\n/plugin marketplace add varmabudharaju/tend\n/plugin install tend@tend\n```\n\nThat's the whole thing — hooks register automatically and the `tend` CLI lands on your PATH. Optional: `tend wrap-statusline` adds the statusline tee (exact context % in anchors + the visible heartbeat); plugins can't wrap the statusline themselves.\n\n**Or via pip** (gets hooks + statusline in one step):\n\n```bash\npython3 -m pip install --user -e .\ntend install-hook        # merges hooks + statusline into ~/.claude/settings.json\n# restart your Claude Code session\n```\n\nThe installer is **non-destructive and reversible**: existing hooks and statusline are preserved and backed up (`settings.json.bak-tend`), and `tend uninstall-hook` puts everything back.\n\n**How you know it's working:** your statusline grows a quiet ` | tend: 3 filed, 29k stale` suffix (just `tend: on` when there's nothing to report), and each session opens with a one-line notice — `tend: restored session state from STATE.md`. Everything else stays out of the chat view — but nothing is hidden: open the transcript view (Ctrl+O) and you'll see every `[tend anchor]` and injection exactly as the model reads it. Out of your face, fully auditable.\n\n## Commands\n\n| Command | Does |\n|---|---|\n| `tend status` | Context %, totals, stale-result tokens, STATE.md freshness |\n| `tend report` | Full ledger: tool results by size, offloads, subagents |\n| `tend handoff` | Show what the next session will auto-load |\n| `tend on` / `tend off` | Global kill switch |\n| `tend install-hook` / `tend uninstall-hook` | Reversible settings.json setup |\n\n## Design principles\n\n- **Fail-open, always.** A tend bug must never break your session. Every hook swallows its own errors (logged to `~/.claude/tend/tend.log`, rotated at 1 MB) and Claude Code continues as if tend weren't there.\n- **Nudge, never block.** tend advises (read this file with a range; this spawn doesn't need the premium model; now is a good compact moment). The one narrow exception: it blocks a *stale auto-compact* exactly once, to protect the notebook.\n- **Exact numbers, not guesses.** Context totals come from the session transcript's own token accounting; the context % comes from a statusline tee — tend measures, it doesn't estimate.\n- **Daemon-less.** No background process. Eight tiny hook invocations that each read state from disk, act, and exit.\n\n## Configuration\n\n`~/.claude/tend/config.yaml`, overridable per project in `\u003cproject\u003e/.claude/tend/config.yaml`. Keys and defaults are in `tend/config.py`. Invalid values fall back to defaults rather than disabling tend.\n\n## Limitations\n\n- **MCP tools with an `outputSchema`**: Claude Code validates replacement outputs against the tool's schema and silently keeps the original when a plain-text excerpt doesn't match. tend therefore skips offloading for `mcp__*` tools whose responses aren't plain strings. Built-in tools (Bash, Grep, Glob, WebFetch) are unaffected.\n- `state_stale_tokens` counts **output tokens** generated since STATE.md was last marked (monotonic across compaction), not context-window growth. Default: 3000.\n\n## Under the hood\n\n### System design — how tend plugs into Claude Code\n\nNo daemon, no background process: Claude Code fires an event, a tiny tend process wakes, reads its state from disk, acts, and exits. Everything durable lives in plain files.\n\n```mermaid\nflowchart TB\n    subgraph cc [\"Claude Code\"]\n        EV[\"8 hook events\"]\n        SL[\"statusline render\"]\n        TR[\"session transcript .jsonl\"]\n    end\n    subgraph tendp [\"tend - one short-lived process per event\"]\n        HK[\"hook.py dispatcher\u003cbr/\u003efail-open wrapper\"]\n        LG[\"ledger.py\u003cbr/\u003eincremental token accounting\"]\n        HD[\"event handlers\u003cbr/\u003eoffload, readguard, agentguard,\u003cbr/\u003eanchor, boundary, precompact, sessionstart\"]\n        SLW[\"statusline.py wrapper\"]\n    end\n    subgraph disk [\"state on disk - ~/.claude/tend/\"]\n        SUM[\"sessions/ID/summary.json\u003cbr/\u003eledger totals + cursor\"]\n        CTX[\"sessions/ID/ctx.json\u003cbr/\u003eexact context metrics\"]\n        OUT[\"sessions/ID/outputs/NNNN.txt\u003cbr/\u003eoffloaded tool outputs\"]\n        FLG[\"sessions/ID/flags.json\"]\n    end\n    ST[\"project/.claude/tend/STATE.md\u003cbr/\u003ethe notebook\"]\n    EV --\u003e|\"stdin JSON\"| HK\n    HK --\u003e LG\n    HK --\u003e HD\n    LG --\u003e|\"reads incrementally\"| TR\n    LG \u003c--\u003e SUM\n    SL --\u003e SLW\n    SLW --\u003e CTX\n    HD \u003c--\u003e FLG\n    HD --\u003e OUT\n    HD \u003c--\u003e ST\n    HD --\u003e|\"stdout JSON: excerpt, anchor,\u003cbr/\u003erestored state, or block\"| EV\n```\n\n### Component view — module layers\n\n```mermaid\nflowchart TB\n    subgraph entry [\"Entry points\"]\n        cli[\"cli.py\"]\n        hookpy[\"hook.py\"]\n        slpy[\"statusline.py\"]\n        inst[\"install.py\"]\n    end\n    subgraph handlers [\"Event handlers\"]\n        off[\"offload\"]\n        rg[\"readguard\"]\n        ag[\"agentguard\"]\n        an[\"anchor\"]\n        bd[\"boundary\"]\n        pc[\"precompact\"]\n        ss[\"sessionstart\"]\n    end\n    subgraph core [\"Core services\"]\n        led[\"ledger\"]\n        stm[\"state\"]\n        adv[\"advisor\"]\n        cm[\"ctxmetrics\"]\n        cfg[\"config\"]\n        flg[\"flags\"]\n        tk[\"tokens\"]\n    end\n    subgraph infra [\"Infrastructure\"]\n        io[\"hookio - fail-open, log rotation\"]\n        pa[\"paths - atomic JSON I/O\"]\n    end\n    hookpy --\u003e handlers\n    cli --\u003e core\n    cli --\u003e inst\n    slpy --\u003e infra\n    handlers --\u003e core\n    core --\u003e infra\n```\n\n### Flow chart — when does tend recommend compaction?\n\nThe advisor runs on every prompt; this is its whole decision:\n\n```mermaid\nflowchart TD\n    p[\"context % from ctx.json\"] --\u003e u{\"at or above\u003cbr/\u003eurge threshold? (70%)\"}\n    u --\u003e|yes| now[\"anchor says:\u003cbr/\u003erun now: /compact + curated instructions\"]\n    u --\u003e|no| a{\"at or above\u003cbr/\u003eadvise threshold? (55%)\"}\n    a --\u003e|no| quiet[\"say nothing\"]\n    a --\u003e|yes| b{\"is this a task boundary?\u003cbr/\u003e(STATE.md was just updated)\"}\n    b --\u003e|yes| good[\"good moment for /compact\"]\n    b --\u003e|no| later[\"at the next task boundary,\u003cbr/\u003erun /compact\"]\n```\n\nAnd the one time tend ever blocks anything:\n\n```mermaid\nflowchart TD\n    ac[\"auto-compact about to fire\"] --\u003e home{\"working in the\u003cbr/\u003ehome directory?\"}\n    home --\u003e|yes| pass[\"allow\"]\n    home --\u003e|no| stale{\"is STATE.md stale?\u003cbr/\u003e(notebook behind the work)\"}\n    stale --\u003e|no| pass\n    stale --\u003e|yes| once{\"already blocked once\u003cbr/\u003ethis session?\"}\n    once --\u003e|yes| pass\n    once --\u003e|no| block[\"block ONCE:\u003cbr/\u003eupdate the notebook, then compact\"]\n```\n\n### Modules\n\n```\ntend/\n  hook.py          entry point: python3 -m tend.hook (all 8 events)\n  hookio.py        stdin/stdout plumbing, fail-open wrapper, log rotation\n  ledger.py        incremental transcript ledger: exact context totals,\n                   tool-result sizes, staleness, crash-safe single-file cursor\n  offload.py       pillar 1: oversized-output offloading\n  readguard.py     pillar 1b: nudge unbounded Reads of large text files\n  agentguard.py    pillar 1c: model-tier nudge for subagent spawns\n  state.py         STATE.md template, parsing, atomic seeding\n  sessionstart.py  pillar 4: state restore into fresh sessions\n  anchor.py        pillar 3: per-prompt anchor (urgency-first truncation)\n  boundary.py      Stop-event task-boundary + staleness detection\n  precompact.py    pillar 4 safety net: snapshot + one-shot stale block\n  advisor.py       when and how to recommend a curated /compact\n  statusline.py    statusline wrapper: tees exact context metrics to disk\n  config.py        defaults \u003c global yaml \u003c project yaml, validated\n  install.py       reversible settings.json merge (backup, mode-preserving)\n  cli.py           status / report / handoff / on / off / (un)install-hook\n```\n\n164 tests (`python3 -m pytest`). Every bug fixed in v0.2 carries a regression test written from the bug's reproduction.\n\n## Battle-tested by its sibling\n\ntend v0.1 was adversarially reviewed by [swarm](https://github.com/varmabudharaju/swarm) — a 9-agent review (6 reviewers + 2 independent verifiers + synthesis) that reproduced every claim against the installed binary before reporting it. The verified report (33 confirmed findings, from a race that silently lost ledger records to a truncation bug that disabled the staleness net right after compaction) is in [`docs/swarm-review-2026-06-10.md`](docs/swarm-review-2026-06-10.md); v0.2 fixed all of them, test-first.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvarmabudharaju%2Ftend","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fvarmabudharaju%2Ftend","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvarmabudharaju%2Ftend/lists"}