Skip to content

feat(persona): decision agent over the persona memory layer (algorithmic retrieval + LLM pass)#77

Merged
senamakel merged 4 commits into
mainfrom
feat/persona-decision-agent
Jul 14, 2026
Merged

feat(persona): decision agent over the persona memory layer (algorithmic retrieval + LLM pass)#77
senamakel merged 4 commits into
mainfrom
feat/persona-decision-agent

Conversation

@senamakel

Copy link
Copy Markdown
Member

Summary

Turns the persona memory layer (doc 06, #75) into an answer surface: a purely algorithmic retriever plus a tinyagents agent harness that answers hard coding decisions as this person would, grounded in their distilled coding persona. Demonstrates both goals from #76's parent milestone follow-on — one full memory-engine run, and user-like intelligence surfaced from it.

Two new pieces alongside the existing persona surface (all persona-gated; default build unaffected):

memory::persona::retrieve — algorithmic retrieval (no LLM)

PersonaRetriever::load(&config) reads every owner="persona" observation leaf the reduce step persists (one Document chunk per facet), splits each rendered - <obs> ("<quote>") [tN] line into an indexed doc, and ranks against a query with:

score = BM25(query; k1=1.5, b=0.75) × tier_weight(tier)   // T0 1.0 … T3 0.4

Deterministic, network-free, ties broken by recency. No per-observation vectors exist on disk (pipeline seals with embedder: None), and embedding the query would make retrieval itself model-dependent — so retrieval stays lexical and the LLM enters only at the final pass.

examples/persona_agent.rs — the decision agent (LLM final pass)

AgentHarness<PersonaState> backed by the OpenRouter reference model (DeepSeek v4 Flash) with three read-only tools over the retriever: search_persona (BM25, optional facet filter), list_directives (verbatim T0 rules), persona_overview (facet coverage). Stage 1 selects candidates algorithmically; the LLM only filters, resolves tier/recency conflicts, and writes a decisive answer in the person's voice — citing the evidence it used.

Live verification

One full backfill over this machine (Claude Code + Codex + CLAUDE.md/AGENTS.md + git), capped 80 sessions:

  • 75 sessions → 927 observations + 1,372 verbatim directives → 8.6 KB PERSONA.md, $0.0848, no fabrication.
  • Three live agent decisions each surfaced verifiable real preferences (dep-vs-hand-roll via the "use the Redis middleware" rule; fix/ branch + failing-test-first + babysit-CI flow; sibling *_tests.rs + ≤500-line + export-only mod.rs).

Full numbers and reproduce steps in docs/plan/06-persona-agent-demo.md.

Tests

  • 10 new retriever unit + integration tests (parsing, tokenizer, BM25 ranking, facet filter, tier weighting, real-workspace load roundtrip).
  • 59 persona tests + base suite unaffected; cargo fmt clean.

Notes

  • The agent loop makes 5–8 model round-trips (~100–190s); drive with a ≥300s timeout.
  • Retrieval is a library-level API (memory::persona::retrieve); the example only wires it to the harness.

🤖 Generated with Claude Code

Adds `memory::persona::retrieve` — a purely lexical, no-LLM retriever over the
persona observation leaves persisted by the reduce step. Loads every
`owner="persona"` Document chunk per facet, splits each rendered `- <obs> (…)
[tN]` line into an indexed observation, and ranks against a query with BM25
scaled by evidence-tier weight (mirrors reduce::tier_score). Deterministic,
network-free; the LLM final pass filters what this surfaces, never selects it.

10 unit + integration tests (parsing, tokenizer, BM25 ranking, facet filter,
tier weighting, real-workspace load roundtrip).
…ols)

Adds `examples/persona_agent.rs` — a two-stage decision agent over the persona
memory layer:
- Stage 1: algorithmic BM25 retrieval (PersonaRetriever, no LLM).
- Stage 2: a tinyagents AgentHarness backed by the OpenRouter model (DeepSeek v4
  Flash) with three read-only tools — search_persona, list_directives,
  persona_overview — that filters/synthesises the retrieved evidence into a
  decision in the developer's own voice, citing tiers.

Registered as a `persona`-gated example.
@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 33 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 94570f5b-5930-4bb7-97b0-311b73f7eaba

📥 Commits

Reviewing files that changed from the base of the PR and between b7d51e4 and d63fa57.

📒 Files selected for processing (6)
  • Cargo.toml
  • docs/plan/06-persona-agent-demo.md
  • examples/persona_agent.rs
  • src/memory/persona/mod.rs
  • src/memory/persona/retrieve.rs
  • src/memory/persona/retrieve_tests.rs

Comment @coderabbitai help to get the list of available commands.

@senamakel senamakel merged commit 9a0603a into main Jul 14, 2026
6 of 7 checks passed

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d63fa57c8d

ℹ️ 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".

Comment thread examples/persona_agent.rs
Comment on lines +215 to +220
"[{facet} | {tier} | score {score:.2}] {text}{quote}",
facet = h.facet.as_str(),
tier = h.tier.as_str(),
score = h.score,
text = h.text,
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Include timestamps in persona evidence output

When retrieved observations conflict, the system prompt tells the LLM to prefer the more recent evidence, but this formatter drops h.timestamp before the hit reaches the model. In same-tier conflicts or across multiple search_persona calls, the model only sees facet/tier/score/text/quote and cannot apply the recency rule, so it can choose stale preferences even though the retriever already carries timestamps.

Useful? React with 👍 / 👎.

source_kind: Some(SourceKind::Document),
source_id: Some(facet.tree_scope()),
owner: Some(PERSONA_OWNER.to_string()),
limit: Some(10_000),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Page through all persona chunks

For large persona workspaces with more than 10,000 chunks in a facet, this single list_chunks call only indexes the first page: ListChunksQuery is paginated and normalized_limit in src/memory/chunks/store_list.rs clamps requests to 10,000 while ordering newest-first. That violates the retriever's "load every observation" contract and silently makes older evidence unretrievable; loop with offset until the final short page instead.

Useful? React with 👍 / 👎.

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.

1 participant