Feature flags are created in minutes and removed never. After a year a codebase carries dozens of permanently-on toggles, dead branches nobody dares touch, and flags referenced in code that no one ever declared. That debt is invisible until it causes an incident.
This agent correlates your flag registry with the actual references in your codebase, classifies every flag, and emits a ranked removal plan — safest and cheapest removals first.
Install
sudo bash install.sh # deps + venv + systemd service
bash install.sh --no-service # venv only
Use
python agent.py scan # flag references found in the code
python agent.py audit # registry x code -> classified findings
python agent.py plan # ranked removal plan (lowest blast radius first)
python agent.py report # human-readable debt report
python agent.py status # config + LLM backend status
python agent.py run --once # one full cycle, writes the report
Point it at your data with FLAGS_REGISTRY and FLAGS_CODE_ROOT — see
CUSTOMIZE.md.
What it detects
| Verdict | Meaning |
|---|---|
ORPHAN |
referenced in code but absent from the registry — nobody owns the behavior |
EXPIRED |
its own expected_removal_date has passed |
ZOMBIE |
declared with zero code references — pure registry rot |
STALE |
100% rolled out for months — the disabled branch is dead code |
RISKY_KILL_SWITCH |
emergency switch with no owner or no code path |
AGED_EXPERIMENT |
experiment past any statistically useful window |
UNOWNED |
no accountable owner |
Each flag gets a weighted score; the total is your debt score. Use
run --once --fail-over-budget as a CI gate.
Bring your own LLM (optional)
Classification is fully deterministic and needs no LLM — the agent is
complete offline. If a coding-agent CLI (Claude Code / Kimi Code / Codex) is
logged in, it is used to review the removal ordering and flag operational
risks, using the prompts in PROMPTS.md. No API key is ever required: your CLI
subscription is the billing relationship.
By design the LLM can only annotate findings the deterministic rules already produced. A hallucinating model cannot invent a flag or change a verdict.
Tests
bash smoke_test.sh # 13 offline tests, no keys, pinned clock
Languages scanned
Python, JS/JSX, TS/TSX, Go, Ruby, Java, Kotlin, PHP, C#, Rust, YAML and JSON,
via 9 built-in SDK call patterns — extend with FLAGS_EXTRA_PATTERNS.
Use a local model (Ollama)
The optional LLM step can also run on your own machine through Ollama, so no text leaves your server. No account and no API key.
ollama pull <model> # any chat model you like
export PACK_LLM=ollama # prefer Ollama even if a coding-agent CLI is installed
export PACK_OLLAMA_MODEL=<model> # optional: default is the first chat model the server lists
python3 -c "import llm_adapter; print(llm_adapter.detect())" # prints: ollama
PACK_OLLAMA_HOSTpoints to another server (defaulthttp://127.0.0.1:11434);OLLAMA_HOSTis also read whenPACK_LLM=ollama.PACK_OLLAMA_NUM_CTXsets the context size if your reports or prompts are long.- Without
PACK_LLM, a local Ollama server is used only when no Claude Code, Kimi Code or Codex CLI is found. - If the server is down or the model is missing, the pack keeps working in its deterministic mode.
Learning memory (local and private)
Every LLM call this pack makes is journaled in ./.pack_memory/ next to the code, so the pack can improve for you. Nothing is ever sent anywhere.
- What is kept: the task name, the masked input, the masked answer, status and latency. Keys, tokens, e-mails, phone numbers, IP addresses, card and IBAN numbers and personal paths are masked before writing; the system prompt is never stored.
- Teach it:
python3 pack_memory.py feedback last okconfirms a result,python3 pack_memory.py feedback last bad --fix "the right answer"corrects it. Confirmed and corrected runs are reused as validated examples when a similar input comes in. Unrated runs are never injected into a prompt. - Find similar cases:
python3 pack_memory.py similar "some text"andpython3 pack_memory.py clusters(groups of near-identical failures) work in plain Python, no model needed. Optional meaning-based matching uses an embedding model you already run locally:python3 pack_memory.py enable-semantic(Ollama on this machine only). - Share, only if you want:
python3 pack_memory.py exportwrites an anonymised summary (counts, statuses, size buckets, no text) for you to review. - Control:
PACK_MEMORY=0turns it off,PACK_MEMORY_STORE_TEXT=0keeps fingerprints only,python3 pack_memory.py purge --yeswipes it.
