feat(tree): flavoured memory trees — ask-driven distillation into a ≤1000-token root markdown artifact#70
Conversation
Add the ask-driven "flavoured" tree family (issue #68): a variant of the source/topic/global bucket-seal trees whose every summarisation step is steered by a natural-language ask. - TreeKind::Flavoured + "flavoured" wire string; SummaryTreeKind::Flavoured with wiki/summaries/flavoured-<scope>/ path routing and front-matter. - Persist the ask on mem_tree_trees via a nullable `ask` column (schema + add_column_if_missing migration); thread through Tree.ask, insert/row_to_tree, and every SELECT. - get_or_create_tree_with_ask stamps the ask at create time. - SummaryContext.ask, populated from the tree row at seal; prepare_summary_prompt emits a flavour-directed profile prompt when an ask is present. - TreeFactory::flavoured(scope, ask) with LabelStrategy::Empty. - TreeConfig::flavour_root_token_budget (default 1000). Claude-Session: https://claude.ai/code/session_01TvyrqfH6dBqs7D6XFdSRRP
The deliverable for issue #68: the top of a flavoured tree compiled into a single, small, always-fresh markdown profile a host can inject verbatim. - compile_flavoured_root(config, tree_id): reads the root SummaryNode body, clamps it to flavour_root_token_budget tokens, and stages it with light YAML front-matter (ask, tree id, scope, sealed_at, leaves_folded evidence changelog, token estimate). - Staged atomically and overwritten in place at flavoured/<scope>.md so hosts read a fixed path; it is a pure projection of the root node, untracked in SQLite. - Recompiled after any seal that touches the root, via a hook in cascade_all_from_with_services (covers append_leaf, flush, force_flush/seal_now). - Unit tests, an end-to-end integration test (tests/flavoured_tree.rs), and a spec page (docs/spec/flavoured-trees.md). Claude-Session: https://claude.ai/code/session_01TvyrqfH6dBqs7D6XFdSRRP
|
Warning Review limit reachedYou’ve reached a temporary PR review limit under our Fair Usage Limits Policy. Next review available in: 44 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (27)
Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 273afe4a89
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| crate::memory::tree::TreeKind::Source => SummaryTreeKind::Source, | ||
| crate::memory::tree::TreeKind::Topic => SummaryTreeKind::Topic, | ||
| crate::memory::tree::TreeKind::Global => SummaryTreeKind::Global, | ||
| crate::memory::tree::TreeKind::Flavoured => SummaryTreeKind::Flavoured, |
There was a problem hiding this comment.
Refresh flavoured artifacts after direct ingest
When a flavoured tree is populated through the documented ingest_summary path, this new match arm allows the ingest to stage a flavoured L1 summary, but the function only recompiles the prompt artifact from the cascade hook. For the common under-fanout case (sealed_ids is empty after line 137), the transaction may have just set root_id to this summary, yet flavoured/<scope>.md is never written or refreshed, so hosts reading the stable compiled-root path see a missing or stale profile until enough direct-ingested summaries trigger a higher-level seal.
Useful? React with 👍 / 👎.
| let budget = config.tree.flavour_root_token_budget; | ||
| let (clamped, token_estimate) = clamp_to_budget(body.trim(), budget); |
There was a problem hiding this comment.
Reserve front-matter space in root budget
When the root summary body is already near flavour_root_token_budget, clamping the body to the full budget and then adding YAML front-matter returns a markdown artifact that exceeds the advertised ≤1000-token compiled-root budget. This happens with ordinary sealed profiles whose body estimates at the configured budget, and gets worse for long asks, while token_estimate still reports only the body size; hosts that reserve the configured budget for the prompt-ready file can overflow their prompt allowance.
Useful? React with 👍 / 👎.
Closes #68.
Summary
Adds a new flavoured tree kind: a variant of the source/topic/global bucket-seal trees that is instantiated with a specific ask/flavour (e.g. "tweet writing style", "coding preferences") and continuously distills everything ingested into it through that lens. The top of the tree compiles into a single, always-fresh markdown file (≤1000 tokens by default) a host can drop verbatim into a prompt as a style/preference profile.
Built entirely on the existing bucket-seal SQLite tree engine (
src/memory/tree/) — same seal-and-cascade machinery, but every summarisation step is steered by the ask, and the root has a first-class compiled artifact.Changes
TreeKind::Flavoured("flavoured"wire string),SummaryTreeKind::Flavouredwithwiki/summaries/flavoured-<scope>/path routing + front-matter, and everymatch tree.kindsite updated.mem_tree_trees.askcolumn (schema + idempotentadd_column_if_missingmigration),Tree.ask: Option<String>threaded through insert /row_to_tree/ every SELECT.get_or_create_tree_with_askstamps it at create time.SummaryContext.ask, populated from the tree row at seal;prepare_summary_promptemits a flavour-directed profile prompt when an ask is present. No LLM coupling — the ask just rides along in the context;ConcatSummariserstays the deterministic fallback.TreeFactory::flavoured(scope, ask)withLabelStrategy::Empty.compile_flavoured_root(config, tree_id) -> Result<String>: reads the rootSummaryNodebody, clamps toflavour_root_token_budgettokens, and stages it atomically (overwritten in place) at a stableflavoured/<scope>.mdwith YAML front-matter (ask, tree id, scope, sealed_at,leaves_foldedevidence changelog, token estimate). Recompiled after any seal that touches the root via a hook incascade_all_from_with_services(coversappend_leaf, flush, andforce_flush_tree/seal_now).TreeConfig::flavour_root_token_budget(default 1000).docs/spec/flavoured-trees.md(contract, wire strings, artifact format).compile_flavoured_root(staging, budget clamp, non-flavoured rejection, auto-recompile) and an end-to-end integration testtests/flavoured_tree.rs.Testing
cargo fmt --all --check— cleancargo clippy --tests— no warningscargo test --lib memory::tree— 124 passedcargo test --test flavoured_tree— 1 passedcargo testgreen (one pre-existing flaky queue test,drain_tolerates_retired_kind_rows, intermittently fails under full-suite parallelism and passes in isolation — unrelated to this change, which touches no queue code).https://claude.ai/code/session_01TvyrqfH6dBqs7D6XFdSRRP