Skip to content

feat(retrieval): graph-expansion fact-readback (bi-temporal Phase 1, default-off) - #191

Merged
jaylfc merged 2 commits into
masterfrom
feat/bitemporal-readback
Jul 26, 2026
Merged

feat(retrieval): graph-expansion fact-readback (bi-temporal Phase 1, default-off)#191
jaylfc merged 2 commits into
masterfrom
feat/bitemporal-readback

Conversation

@jaylfc

@jaylfc jaylfc commented Jul 9, 2026

Copy link
Copy Markdown
Owner

What this wires

The bi-temporal fact store was write-only at answer time: facts are extracted, validity-windowed, superseded, and never read back into what the generator sees. This PR wires the previously orphaned graph_expansion.expand_from_results (built, referenced nowhere in retrieval.py / api.py / controls.py) into retrieve(), behind a new default-off control.

When graph_expansion is on and a "kg" source is present, the entities in the served hits are looked up in the already-populated bi-temporal knowledge graph (query_entity) and their currently-valid facts are formatted (format_expanded_context) into one additional derived-facts block appended to the results. This is almost entirely wiring: the store layer (query_entity(as_of=...) with valid_from/valid_to/superseded_by filtering) and the integration layer (expand_from_results) already existed.

Design

  • Control: graph_expansion (runtime, quality) added to taosmd/controls.py. Type int: the value is the derived block's token budget (max_tokens), and 0 = off. Default 0 (off). Wired through the runtime controls into search() exactly like adjacent_turns -> adjacent_neighbors.
  • Injection point: retrieve() gains a graph_expansion: int = 0 parameter (mirrors adjacent_neighbors). In all four strategies (thorough / fast / minimal / custom), after the source merge, truncation, and neighbour attachment, _append_graph_expansion(results, sources.get("kg"), graph_expansion) appends the derived block as additional context (not counted against limit), following the apply_temporal_stage injection pattern.
  • Provenance-honest: the block is labelled derived (source == "kg_expansion", derived == True, metadata.kind == "kg_facts"), confidence carried from the strongest currently-valid triple. The underlying turns remain the primary evidence; the claims gate is untouched.
  • Fail-open: a missing / None / empty / erroring KG, no entities, or an empty budget is a silent no-op. No new storage, schema, or extraction change.

Phase 1 vs Phase 2 boundary

  • Phase 1 (this PR): valid-time only. expand_from_results passes as_of=None, so query_entity reads currently-valid facts (as_of=now). No as-of parameter is exposed on the public search / HTTP surface.
  • Phase 2 (not here): expose as_of on search/retrieve and HTTP, resolved from the query's temporal expression, calling the already-present query_entity(as_of=) path. Pre-registered separately (E-026).

Default-off / byte-identical

With the control off (the default), the retrieval path is byte-for-byte unchanged: the if graph_expansion: guard is false, _append_graph_expansion is never called, and existing callers are unaffected by the new keyword-only default. A regression test asserts the default run and an explicit graph_expansion=0 run are equal with no kg_expansion source.

Tests

TDD, added to tests/test_retrieval.py: off-is-noop regression guard; on-appends-one-derived-block; empty-KG / erroring-KG / no-kg-source no-ops (fail-open); and a token-budget test (tight budget drops facts and stays within max_tokens*4 chars, generous budget keeps more). Full suite: 1086 passed.

Validation pending

EventQA validation (E-025, pre-registered) is a separate GPU step and is pending. The feature is left ready for it. Do not flip the default until E-025 clears its kill criterion (> standard-error improvement on EventQA overall, <= 20% context-token increase).

HOLD for review — do not merge.

@qodo-code-review

Copy link
Copy Markdown

Qodo reviews are paused for this user.

Troubleshooting steps vary by plan Learn more →

On a Teams plan?
Reviews resume once this user has a paid seat and their Git account is linked in Qodo.
Link Git account →

Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center?
These require an Enterprise plan - Contact us
Contact us →

@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@jaylfc, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 41 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 8f7477eb-aa5f-406a-970d-6a6273d1404b

📥 Commits

Reviewing files that changed from the base of the PR and between 2c0a751 and 39c7d65.

📒 Files selected for processing (7)
  • CHANGELOG.md
  • README.md
  • docs/INTEGRATION-memory-config.md
  • taosmd/api.py
  • taosmd/controls.py
  • taosmd/retrieval.py
  • tests/test_retrieval.py
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/bitemporal-readback

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@gitar-bot

gitar-bot Bot commented Jul 9, 2026

Copy link
Copy Markdown

Gitar is working

Gitar

Comment thread taosmd/retrieval.py
"derived": True,
"metadata": {"derived": True, "kind": "kg_facts", "triples": expanded},
}
return [*results, derived]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

WARNING: Derived block is not project/agent scoped, so it can leak cross-project KG facts into a scoped answer.

This block is built from the global TemporalKnowledgeGraph (which has no project dimension; query_entity never receives one) and is appended here before _filter_project_scope. Because the block's metadata has no project/agent key, that filter never drops it (hit_project stays None). The primary served hits are project-filtered, but the KG readback surfaces facts about the discovered entities regardless of which project learned them. Under a project-scoped query, the derived block can expose facts sourced from other projects' turns.

Consider threading project/search_agents into _append_graph_expansion and filtering format_expanded_context/the expanded triples by project scope, or document explicitly that the derived block intentionally crosses project boundaries.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

Comment thread taosmd/retrieval.py
"rank": len(results),
"source_score": top_confidence,
"derived": True,
"metadata": {"derived": True, "kind": "kg_facts", "triples": expanded},

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

SUGGESTION: metadata["triples"] stores the full expanded list (up to max_expanded=10) while text is truncated to the token budget by format_expanded_context.

The visible block and the structured triples can disagree: text may show 1 fact but metadata.triples exposes up to 10. Any downstream consumer that reads metadata.triples (e.g. a claims gate or provenance checker) will see facts that were never surfaced in text, undermining the "provenance-honest / what you see is what's served" guarantee. Store only the budget-truncated subset, or explicitly document that triples is the superset.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

Comment thread taosmd/retrieval.py
if not block:
return results
# Keep confidence: surface the strongest currently-valid triple's score.
top_confidence = max(

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

SUGGESTION: top_confidence is the max confidence across all expanded triples, not just the ones that fit the budget and appear in text.

format_expanded_context truncates the visible facts to max_tokens*4 chars, but source_score here reflects the strongest triple in the whole (pre-truncation) expanded list. The score attached to the derived result can therefore overstate the confidence of the facts actually shown. Compute top_confidence from only the triples included in block (e.g. have format_expanded_context return the kept subset) for an honest source_score.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

Comment thread taosmd/controls.py
id="graph_expansion", label="Graph-expansion fact readback",
category="quality", scope="runtime", type="int",
config_key="controls.graph_expansion", default=0, int_range=(0, 2000),
cost="one KG read over the served hits' entities plus the added context tokens; the value is the derived block's token budget",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

SUGGESTION: Cost text says "one KG read over the served hits' entities", but expand_from_results issues a query_entity call per seed entity (capped at 10 via unique_entities[:10]) plus BFS follow-ups (default max_hops=2, max_expanded=10).

So a single enabled query can perform ~10+ KG lookups, not one. The cost description (and the README/doc entries derived from it) understates the read amplification. Worth rephrasing to reflect the per-entity fan-out, or capping seed entities lower for the readback path.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

@kilo-code-bot

kilo-code-bot Bot commented Jul 9, 2026

Copy link
Copy Markdown

Code Review Summary

Status: 4 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 1
SUGGESTION 3
Issue Details (click to expand)

WARNING

File Line Issue
taosmd/retrieval.py 669 Derived KG block is not project/agent scoped; under a project-scoped query it bypasses _filter_project_scope (no project metadata) and can surface cross-project KG facts learned from other projects' turns.

SUGGESTION

File Line Issue
taosmd/retrieval.py 667 metadata["triples"] stores the full expanded list (up to 10) while text is token-budget truncated; structured triples disagree with visible block.
taosmd/retrieval.py 657 top_confidence/source_score uses max confidence across all triples, not only the budget-shown ones, overstating shown-fact confidence.
taosmd/controls.py 108 Cost says "one KG read" but expand_from_results fans out to ~10+ query_entity calls per seed entity + BFS; cost understated.
Files Reviewed (7 files)
  • taosmd/retrieval.py - 3 issues (all re-verified on current HEAD 39c7d65)
  • taosmd/controls.py - 1 issue (re-verified)
  • taosmd/api.py - 0 issues (override wiring consistent with adjacent_neighbors)
  • tests/test_retrieval.py - 0 issues (regression + fail-open + budget + None-confidence tests look correct)
  • README.md - 0 issues
  • docs/INTEGRATION-memory-config.md - 0 issues
  • CHANGELOG.md - 0 issues

Note: the previous review SHA 37c35bc is no longer in history (force-pushed to 9286e47 + 39c7d65). The hardening commit 39c7d65 added a confidence=None fail-open fix plus tests but did not address the 4 findings below, all of which remain valid on the current code.

Fix these issues in Kilo Cloud

Previous Review Summary (commit 37c35bc)

Current summary above is authoritative. Previous snapshots are kept for context only.

Previous review (commit 37c35bc)

Status: 4 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 1
SUGGESTION 3
Issue Details (click to expand)

WARNING

File Line Issue
taosmd/retrieval.py 667 Derived KG block is not project/agent scoped; under a project-scoped query it bypasses _filter_project_scope (no project metadata) and can surface cross-project KG facts learned from other projects' turns.

SUGGESTION

File Line Issue
taosmd/retrieval.py 665 metadata["triples"] stores the full expanded list (up to 10) while text is token-budget truncated; structured triples disagree with visible block.
taosmd/retrieval.py 655 top_confidence/source_score uses max confidence across all triples, not only the budget-shown ones, overstating shown-fact confidence.
taosmd/controls.py 108 Cost says "one KG read" but expand_from_results fans out to ~10+ query_entity calls per seed entity + BFS; cost understated.
Files Reviewed (7 files)
  • taosmd/retrieval.py - 3 issues
  • taosmd/controls.py - 1 issue
  • taosmd/api.py - 0 issues (override wiring consistent with adjacent_neighbors)
  • tests/test_retrieval.py - 0 issues (regression + fail-open + budget tests look correct)
  • README.md - 0 issues
  • docs/INTEGRATION-memory-config.md - 0 issues
  • CHANGELOG.md - 0 issues

Fix these issues in Kilo Cloud


Reviewed by hy3-20260706:free · Input: 60.2K · Output: 4.7K · Cached: 227.1K

jaylfc added 2 commits July 10, 2026 00:46
…default-off)

Wire the orphaned graph_expansion.expand_from_results into retrieve() behind a
new default-off control. When graph_expansion is enabled and a "kg" source is
present, the entities in the served hits are looked up in the already-populated
bi-temporal knowledge graph and their currently-valid facts are appended as one
derived-facts block (source "kg_expansion", derived=True), reading the fact
store back into the answer context.

Phase 1 is valid-time only (query_entity defaults to as_of=now); no as-of query
surface is exposed (that is Phase 2). No new storage, schema, or extraction
change. Provenance-honest: the block is labelled derived with confidence
carried from the strongest triple, the underlying turns stay the primary
evidence, and the claims gate is untouched. Fail-open: a missing, empty, or
erroring KG is a silent no-op, and with the control off the retrieval path is
byte-for-byte unchanged.

The graph_expansion control is an int token budget (0 = off, the default),
wired through the runtime controls into search() like adjacent_turns. EventQA
validation (E-025) is pending before any default flip.
@jaylfc
jaylfc force-pushed the feat/bitemporal-readback branch from 37c35bc to 39c7d65 Compare July 9, 2026 23:48
jaylfc added a commit that referenced this pull request Jul 21, 2026
…MemEval with an instrument-validity gate

Phase-1 feasibility done before the design, per the N-026 lesson. The
LongMemEval runner does not call retrieve() today (hand-assembles context
at lines 358-403), extraction is regex-only, and the encounter-order seed
cap lands 2.3 of 10 seeds on the KG. But the corpus coverage claim holds
(untruncated per-turn extraction, 482 triples per question) and a synthetic
end-to-end probe through PR #191 fires 6/6 with the served context differing
6/6 at 0.111 saturation, against the 0.72 that doomed EventQA. So the
instrument can see the effect, conditional on wiring the runner first.

Instrument-validity gate and kill criterion both fixed verbatim before any
arm runs. No results, no arm run, PR #191 unmerged, defaults unchanged.
@jaylfc
jaylfc merged commit 427edb2 into master Jul 26, 2026
2 checks passed
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