Skip to content

Add /topics and /drop-topic commands#4941

Draft
kimjune01 wants to merge 19 commits intoAider-AI:mainfrom
kimjune01:feat/topics-codex-impl
Draft

Add /topics and /drop-topic commands#4941
kimjune01 wants to merge 19 commits intoAider-AI:mainfrom
kimjune01:feat/topics-codex-impl

Conversation

@kimjune01
Copy link

@kimjune01 kimjune01 commented Mar 19, 2026

Depends on #4940. Review after that PR merges.

Why two PRs

PR 1 (#4940) adds the backend — an opt-in union-find summarizer that maintains topic clusters. It changes nothing by default and can be evaluated on its own merits: quality-equivalent output, mandatory fallback to recursive, 57 tests.

This PR adds the user-facing commands that make the structured backend useful:

  • /topics — see what the model remembers, with token counts
  • /drop-topic N — remove one stale topic without wiping everything

Splitting lets the maintainer evaluate the data structure separately from the UX surface. The backend is the risky part; the commands are straightforward.

What it does

/drop-topic updates done_messages immediately (same pattern as /clear). Both commands refuse during background summarization. Without --chat-history-summarizer union-find, they show a guidance message.

Addresses #3607 (selective history control), #2219 (see/edit context), #948 (token breakdown with actions).

Test plan

  • 67 tests passing, 565 total, zero regressions
  • Integration test: feed multi-topic conversation → drop topic → verify done_messages excludes dropped content → verify forest rebuild

🤖 Generated with Claude Code and Codex

kimjune01 and others added 5 commits March 18, 2026 15:47
…ry-summarizer)

Port 4 modules from standalone implementation (145 existing tests):
- context_window.py: Forest (union-find clusters) + ContextWindow (hot/cold zones)
- embedding_service.py: Pure Python TF-IDF embedder (no new dependencies)
- cluster_summarizer.py: Per-cluster summarization via model cascade
- chat_summary_uf.py: ChatSummaryUF(ChatSummary) drop-in subclass

Integration: --chat-history-summarizer union-find flag in args.py,
conditional construction in main.py. Default unchanged (recursive).

Backend fixes applied during port:
- Stable root ordering via _root_order list (deterministic render output)
- Weighted centroid averaging in union() (prevents cluster identity distortion)

Safety: mandatory fallback to recursive if output exceeds budget, stale-safety
preserved via _fed_count mechanism, same tokenizer and model cascade.

49 new tests covering 12 areas. 523 existing tests unaffected.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
8 tests with realistic 3-topic conversation (path traversal, Windows drive
letters, file descriptor leak): cluster formation, output format, cold+hot
render, distinct topic clustering, recursive fallback, stale rebuild,
system message filtering, deterministic rendering.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Fix hot tail mismatch: track fed message indices so hot_count maps back
  to correct original messages even with system/tool messages interspersed
- Fix unbounded _hot growth: trim graduated entries after each append
- Fix empty embedding drop in union(): preserve non-empty side
- Remove unused Forest.is_dirty() and Forest.dirty_inputs()
- Remove list-vector branches from _cosine_similarity and union()
  (all embeddings are sparse dicts from TFIDFEmbedder)
- Add tests: mixed-role preservation (2), memory bounds (2)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- resolve_dirty() failure now falls back to recursive instead of crashing
- Remove _maybe_evict() and evict_at parameter — dead code since
  _maybe_graduate() already keeps hot zone at <= graduate_at
- Update all tests to remove evict_at references

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Codex (GPT-5.4) independent implementation of PR 2 from spec.
67 tests passing, 565 total, zero regressions.

Co-Authored-By: codex (GPT-5.4) <noreply@openai.com>
@kimjune01
Copy link
Author

Demo output (mock model, simulated 40-message session)

Five distinct topics: database migrations, JWT auth, React components, CI/CD, payment processing.

aider> /topics

Chat history topics:

  1.    15 tokens - "I discussed The migration needs a down method too in case we need to rol"
  2.    15 tokens - "I discussed Can we also add an index on the verified column for faster q"
  3.    14 tokens - "I discussed Updated the token payload to include exp set to 24h from now"
  4.    10 tokens - "I discussed I will add a Skeleton component using react-loading-skeleton"
  + 337 tokens - 26 recent messages (not yet compressed)

Total: 434 tokens

aider> /drop-topic 1
Dropped topic 1 (15 tokens freed).

aider> /topics

Chat history topics:

  1.    15 tokens - "I discussed Can we also add an index on the verified column for faster q"
  2.    14 tokens - "I discussed Updated the token payload to include exp set to 24h from now"
  3.    10 tokens - "I discussed I will add a Skeleton component using react-loading-skeleton"
  + 337 tokens - 26 recent messages (not yet compressed)

Total: 419 tokens

To try it yourself:

cd ~/Documents/aider
git checkout feat/topics-codex-impl
source .venv/bin/activate
python -m aider --chat-history-summarizer union-find
# Have 30+ exchanges, then:
# /topics
# /drop-topic N

@CLAassistant
Copy link

CLAassistant commented Mar 19, 2026

CLA assistant check
All committers have signed the CLA.

kimjune01 and others added 14 commits March 18, 2026 20:39
Non-root members' _content and _embedding are only needed before merge
(for dirty input collection and centroid computation). After union(),
only the root's centroid and summary matter. Without cleanup, _content
grows unbounded in long sessions.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: aider (claude-sonnet-4-5) <aider@aider.chat>
Co-authored-by: aider (claude-sonnet-4-5) <aider@aider.chat>
Co-authored-by: aider (claude-sonnet-4-5) <aider@aider.chat>
Co-authored-by: aider (claude-sonnet-4-5) <aider@aider.chat>
Co-authored-by: aider (claude-sonnet-4-5) <aider@aider.chat>
Control-loop bug: summarization triggers on tokens (too_big) but
graduation triggers on message count (graduate_at=26). Token budget
fires first, no cold clusters exist, falls back to recursive every
time. Union-find path was unreachable in real usage.

Fix: when summarize() runs with no cold clusters and >4 hot messages,
force_graduate() moves the oldest half to the cold forest. This breaks
the deadlock and lets clusters form before rendering.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… union()

Co-authored-by: aider (claude-sonnet-4-5) <aider@aider.chat>
Co-authored-by: aider (claude-sonnet-4-5) <aider@aider.chat>
Co-authored-by: aider (claude-sonnet-4-5) <aider@aider.chat>
Co-authored-by: aider (claude-sonnet-4-5) <aider@aider.chat>
…ic interpretation

Co-authored-by: aider (claude-sonnet-4-5) <aider@aider.chat>
Co-authored-by: aider (claude-sonnet-4-5) <aider@aider.chat>
Token-aware graduation: keep only as many hot messages as fit in 25%
of max_tokens. Replaces fixed graduate_at=26 which caused a control-loop
deadlock (too_big fires on tokens, graduation on message count — never aligned).

Integration tests use real model tokenizer (litellm) with mock LLM calls.
Tests: cold clusters form, result fits budget, /topics shows clusters,
/drop-topic removes them, repeated cycles work, low budget falls back.

These tests would have caught both the control-loop bug and the budget
overflow that were only discovered during manual testing.

Removes debug logging from chat_summary_uf.py and base_coder.py.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants