context-engineering-core 0.12.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. context_engine/__init__.py +15 -0
  2. context_engine/__main__.py +3 -0
  3. context_engine/_sqlite.py +19 -0
  4. context_engine/_windows_security.py +125 -0
  5. context_engine/_windows_storage.py +110 -0
  6. context_engine/adapters.py +148 -0
  7. context_engine/answers.py +90 -0
  8. context_engine/budget.py +93 -0
  9. context_engine/cli.py +222 -0
  10. context_engine/config.py +193 -0
  11. context_engine/context.py +116 -0
  12. context_engine/demo.py +98 -0
  13. context_engine/errors.py +91 -0
  14. context_engine/evaluation/__init__.py +1 -0
  15. context_engine/evaluation/commands.py +90 -0
  16. context_engine/evaluation/corpus.py +247 -0
  17. context_engine/evaluation/data/freeze.json +26 -0
  18. context_engine/evaluation/data/ground_truth.json +287 -0
  19. context_engine/evaluation/data/pin_retention.json +8 -0
  20. context_engine/evaluation/data/protocol.json +122 -0
  21. context_engine/evaluation/data/scenario.json +1226 -0
  22. context_engine/evaluation/data/summary_fixture.json +7 -0
  23. context_engine/evaluation/execution.py +538 -0
  24. context_engine/evaluation/fixtures.py +32 -0
  25. context_engine/evaluation/grading.py +62 -0
  26. context_engine/evaluation/harness.py +165 -0
  27. context_engine/evaluation/heldout/__init__.py +260 -0
  28. context_engine/evaluation/heldout/__main__.py +40 -0
  29. context_engine/evaluation/heldout/data/ground_truth.json +18 -0
  30. context_engine/evaluation/heldout/data/manifest.json +9 -0
  31. context_engine/evaluation/heldout/data/protocol.json +14 -0
  32. context_engine/evaluation/heldout/data/scenarios.json +18 -0
  33. context_engine/evaluation/heldout/data/scoring_manifest.json +4 -0
  34. context_engine/evaluation/heldout/data/scoring_protocol.json +12 -0
  35. context_engine/evaluation/heldout/scoring.py +176 -0
  36. context_engine/evaluation/protocol.py +264 -0
  37. context_engine/evaluation/reporting.py +294 -0
  38. context_engine/evaluation/results.py +331 -0
  39. context_engine/evaluation/run_commands.py +146 -0
  40. context_engine/evaluation/strategies.py +261 -0
  41. context_engine/inspection.py +266 -0
  42. context_engine/layers/__init__.py +18 -0
  43. context_engine/layers/cap.py +99 -0
  44. context_engine/layers/common.py +162 -0
  45. context_engine/layers/pin.py +68 -0
  46. context_engine/layers/retrieve.py +233 -0
  47. context_engine/layers/summarize.py +202 -0
  48. context_engine/layers/window.py +29 -0
  49. context_engine/mcp_server.py +267 -0
  50. context_engine/memory/__init__.py +5 -0
  51. context_engine/memory/codec.py +91 -0
  52. context_engine/memory/demo.py +90 -0
  53. context_engine/memory/store.py +770 -0
  54. context_engine/models.py +384 -0
  55. context_engine/pipeline.py +383 -0
  56. context_engine/providers/__init__.py +1 -0
  57. context_engine/providers/client.py +301 -0
  58. context_engine/providers/commands.py +138 -0
  59. context_engine/providers/contracts.py +324 -0
  60. context_engine/providers/credentials.py +66 -0
  61. context_engine/providers/memory.py +112 -0
  62. context_engine/providers/store.py +408 -0
  63. context_engine/providers/transport.py +195 -0
  64. context_engine/providers/windows_credentials.py +23 -0
  65. context_engine/py.typed +0 -0
  66. context_engine/service/__init__.py +1 -0
  67. context_engine/service/__main__.py +68 -0
  68. context_engine/service/app.py +404 -0
  69. context_engine/service/auth.py +132 -0
  70. context_engine/service/client.py +52 -0
  71. context_engine/service/control.py +134 -0
  72. context_engine/service/demo.py +151 -0
  73. context_engine/service/integrations.py +28 -0
  74. context_engine/service/schemas.py +59 -0
  75. context_engine/service/worker.py +59 -0
  76. context_engine/service/workers.py +156 -0
  77. context_engine/summaries.py +196 -0
  78. context_engine/tokens.py +171 -0
  79. context_engine/work.py +46 -0
  80. context_engineering_core-0.12.0.dist-info/METADATA +203 -0
  81. context_engineering_core-0.12.0.dist-info/RECORD +84 -0
  82. context_engineering_core-0.12.0.dist-info/WHEEL +4 -0
  83. context_engineering_core-0.12.0.dist-info/entry_points.txt +3 -0
  84. context_engineering_core-0.12.0.dist-info/licenses/LICENSE +202 -0
@@ -0,0 +1,18 @@
1
+ """Five independently callable context layers. No inference is performed."""
2
+
3
+ from .cap import CapLayer
4
+ from .pin import PinLayer
5
+ from .retrieve import RetrieveLayer
6
+ from .summarize import FrozenSummaryPolicy, PrefixSummaryPolicy, SummaryLayer, SummarySnapshot
7
+ from .window import WindowLayer
8
+
9
+ __all__ = [
10
+ "CapLayer",
11
+ "PinLayer",
12
+ "RetrieveLayer",
13
+ "WindowLayer",
14
+ "SummaryLayer",
15
+ "FrozenSummaryPolicy",
16
+ "PrefixSummaryPolicy",
17
+ "SummarySnapshot",
18
+ ]
@@ -0,0 +1,99 @@
1
+ """Deterministic code-point-safe head/tail compression of working messages."""
2
+
3
+ from dataclasses import dataclass, replace
4
+
5
+ from ..config import CapConfig
6
+ from ..context import ContextCarrier, LayerDiagnostic
7
+ from ..errors import CapBudgetError, LayerStateError
8
+ from ..tokens import TokenCounter
9
+ from ..work import Cancellation, raise_if_cancelled
10
+
11
+
12
+ def bounded_piece(
13
+ text: str,
14
+ allowance: int,
15
+ counter: TokenCounter,
16
+ *,
17
+ tail: bool = False,
18
+ cancellation: Cancellation | None = None,
19
+ ) -> str:
20
+ """Find a fitting prefix/suffix; never assume BPE counts are strictly monotone."""
21
+ low, high = 0, len(text)
22
+ best = ""
23
+ while low <= high:
24
+ raise_if_cancelled(cancellation)
25
+ middle = (low + high) // 2
26
+ candidate = text[len(text) - middle :] if tail else text[:middle]
27
+ if counter.count_text(candidate) <= allowance:
28
+ best = candidate
29
+ low = middle + 1
30
+ else:
31
+ high = middle - 1
32
+ return best
33
+
34
+
35
+ @dataclass(frozen=True)
36
+ class CapLayer:
37
+ counter: TokenCounter
38
+ config: CapConfig = CapConfig()
39
+
40
+ def cap_text(self, text: str, *, cancellation: Cancellation | None = None) -> str:
41
+ raise_if_cancelled(cancellation)
42
+ if self.counter.count_text(text) <= self.config.max_tokens:
43
+ return text
44
+ head = bounded_piece(text, self.config.head_tokens, self.counter, cancellation=cancellation)
45
+ tail = bounded_piece(
46
+ text, self.config.tail_tokens, self.counter, tail=True, cancellation=cancellation
47
+ )
48
+ # At least one original code point at both ends is required for a capped message.
49
+ head, tail = head or text[:1], tail or text[-1:]
50
+ while head and tail:
51
+ raise_if_cancelled(cancellation)
52
+ end = len(text) - len(tail)
53
+ omitted = text[len(head) : end]
54
+ marker = f"\n[... {self.counter.count_text(omitted)} tokens elided ...]\n"
55
+ candidate = head + marker + tail
56
+ if omitted and self.counter.count_text(candidate) <= self.config.max_tokens:
57
+ return candidate
58
+ if len(head) >= len(tail) and len(head) > 1:
59
+ head = head[:-1]
60
+ elif len(tail) > 1:
61
+ tail = tail[1:]
62
+ else:
63
+ break
64
+ raise CapBudgetError(
65
+ "CAP allocation cannot fit a head, elision marker and tail",
66
+ max_tokens=self.config.max_tokens,
67
+ )
68
+
69
+ def apply(self, context: ContextCarrier) -> ContextCarrier:
70
+ if context.plan is not None:
71
+ raise LayerStateError("CAP must run before budget planning")
72
+ turns, changed = [], []
73
+ before = after = 0
74
+ for turn in context.original_turns:
75
+ raise_if_cancelled(context.cancellation)
76
+ messages = []
77
+ for message in turn.messages:
78
+ content = self.cap_text(message.content, cancellation=context.cancellation)
79
+ before += self.counter.count_text(message.content)
80
+ after += self.counter.count_text(content)
81
+ messages.append(replace(message, content=content))
82
+ working = replace(turn, messages=tuple(messages))
83
+ turns.append(working)
84
+ if working != turn:
85
+ changed.append(turn.turn_id)
86
+ return replace(
87
+ context,
88
+ working_turns=tuple(turns),
89
+ diagnostics=context.diagnostics
90
+ + (
91
+ LayerDiagnostic(
92
+ "CAP",
93
+ "messages_capped" if changed else "unchanged",
94
+ tuple(changed),
95
+ before,
96
+ after,
97
+ ),
98
+ ),
99
+ )
@@ -0,0 +1,162 @@
1
+ """Shared rendering/accounting primitives used by layers and the public pipeline."""
2
+
3
+ import hashlib
4
+ from dataclasses import asdict, replace
5
+
6
+ from ..context import ContextCarrier, LayerDiagnostic
7
+ from ..errors import LayerStateError
8
+ from ..models import (
9
+ BlockKind,
10
+ ChatRequest,
11
+ ContextBlock,
12
+ Message,
13
+ Role,
14
+ ToolDefinition,
15
+ Turn,
16
+ canonical_json,
17
+ )
18
+ from ..tokens import TokenCounter
19
+ from ..work import raise_if_cancelled
20
+
21
+ ORDER = tuple(BlockKind)
22
+ OPTIONAL = (BlockKind.SUMMARY, BlockKind.WINDOW, BlockKind.RETRIEVED)
23
+
24
+
25
+ def history_digest(turns: tuple[Turn, ...]) -> str:
26
+ values = [
27
+ (asdict(t.scope), t.turn_id, t.revision, t.timestamp.isoformat(), t.content_hash)
28
+ for t in turns
29
+ ]
30
+ return hashlib.sha256(canonical_json(values).encode()).hexdigest()
31
+
32
+
33
+ def input_digest(context: ContextCarrier) -> str:
34
+ pins = []
35
+ for pin in context.pins.pins:
36
+ value = asdict(pin)
37
+ value["effective_at"] = pin.effective_at.isoformat()
38
+ value["expires_at"] = pin.expires_at.isoformat() if pin.expires_at else None
39
+ pins.append(value)
40
+ values = {
41
+ "scope": asdict(context.scope),
42
+ "original": history_digest(context.original_turns),
43
+ "working": history_digest(context.working_turns),
44
+ "pins": pins,
45
+ "question": context.question.to_wire(),
46
+ "question_id": context.question.message_id,
47
+ }
48
+ return hashlib.sha256(canonical_json(values).encode()).hexdigest()
49
+
50
+
51
+ def render_request(
52
+ blocks: tuple[ContextBlock, ...], question: Message, tools: tuple[ToolDefinition, ...] = ()
53
+ ) -> ChatRequest:
54
+ """Quote all historical/evidence blocks as user data, including historical tool calls."""
55
+ by_kind = {block.kind: block for block in blocks}
56
+ messages = []
57
+ for kind in ORDER:
58
+ if kind == BlockKind.QUESTION:
59
+ messages.append(Message("ctx:question", Role.USER, question.content))
60
+ continue
61
+ block = by_kind.get(kind)
62
+ if block is None or not block.content:
63
+ continue
64
+ role = Role.SYSTEM if kind == BlockKind.SYSTEM else Role.USER
65
+ content = (
66
+ block.content
67
+ if kind == BlockKind.SYSTEM
68
+ else f"[{kind.value.upper()} DATA]\n{block.content}"
69
+ )
70
+ messages.append(Message(f"ctx:{kind.value}", role, content))
71
+ return ChatRequest(tuple(messages), tools)
72
+
73
+
74
+ def required_blocks(context: ContextCarrier) -> tuple[ContextBlock, ...]:
75
+ return tuple(b for b in context.blocks if b.kind not in OPTIONAL)
76
+
77
+
78
+ def require_prepared(context: ContextCarrier, counter: TokenCounter) -> None:
79
+ if context.plan is None or context.required_request is None:
80
+ raise LayerStateError("PIN must reserve mandatory context first")
81
+ if context.prepared_fingerprint != input_digest(context):
82
+ raise LayerStateError("History, question or pins changed after budget planning; rerun PIN")
83
+ request = render_request(
84
+ required_blocks(context), context.question, context.required_request.tools
85
+ )
86
+ if (
87
+ request != context.required_request
88
+ or counter.count_request(request) != context.plan.required_estimate
89
+ ):
90
+ raise LayerStateError("Mandatory blocks or counting profile changed after planning")
91
+
92
+
93
+ def set_block(
94
+ context: ContextCarrier, block: ContextBlock, diagnostic: LayerDiagnostic
95
+ ) -> ContextCarrier:
96
+ blocks = tuple(b for b in context.blocks if b.kind != block.kind) + (block,)
97
+ return replace(
98
+ context,
99
+ blocks=tuple(sorted(blocks, key=lambda b: ORDER.index(b.kind))),
100
+ diagnostics=context.diagnostics + (diagnostic,),
101
+ )
102
+
103
+
104
+ def window_block(
105
+ context: ContextCarrier, ids: tuple[str, ...], counter: TokenCounter
106
+ ) -> ContextBlock:
107
+ selected = [t for t in context.working_turns if t.turn_id in ids]
108
+ content = (
109
+ canonical_json(
110
+ [{"turn": t.turn_id, "messages": [m.to_wire() for m in t.messages]} for t in selected]
111
+ )
112
+ if selected
113
+ else ""
114
+ )
115
+ return ContextBlock(BlockKind.WINDOW, content, text_tokens=counter.count_text(content))
116
+
117
+
118
+ def incremental_cost(context: ContextCarrier, block: ContextBlock, counter: TokenCounter) -> int:
119
+ request = render_request(
120
+ required_blocks(context) + (block,), context.question, context.required_request.tools
121
+ )
122
+ return max(
123
+ 0,
124
+ counter.count_request(request).estimated_tokens
125
+ - context.plan.required_estimate.estimated_tokens,
126
+ )
127
+
128
+
129
+ def fits_block(
130
+ context: ContextCarrier, block: ContextBlock, allowance: int, counter: TokenCounter
131
+ ) -> bool:
132
+ if incremental_cost(context, block, counter) > allowance:
133
+ return False
134
+ blocks = tuple(b for b in context.blocks if b.kind != block.kind) + (block,)
135
+ # RETRIEVE runs first but must leave the exact planned WINDOW viable.
136
+ if context.window_planned and block.kind != BlockKind.WINDOW:
137
+ blocks = tuple(b for b in blocks if b.kind != BlockKind.WINDOW) + (
138
+ window_block(context, context.plan.window_turn_ids, counter),
139
+ )
140
+ request = render_request(blocks, context.question, context.required_request.tools)
141
+ return counter.count_request(request).estimated_tokens <= context.plan.input_allowance
142
+
143
+
144
+ def plan_window(context: ContextCarrier, counter: TokenCounter) -> ContextCarrier:
145
+ require_prepared(context, counter)
146
+ selected: tuple[str, ...] = ()
147
+ for turn in reversed(context.working_turns):
148
+ raise_if_cancelled(context.cancellation)
149
+ candidate = (turn.turn_id,) + selected
150
+ if (
151
+ incremental_cost(context, window_block(context, candidate, counter), counter)
152
+ > context.plan.window_tokens
153
+ ):
154
+ break
155
+ selected = candidate
156
+ if context.window_planned:
157
+ if selected != context.plan.window_turn_ids:
158
+ raise LayerStateError("Stored WINDOW membership differs from shared planner")
159
+ return context
160
+ return replace(
161
+ context, plan=replace(context.plan, window_turn_ids=selected), window_planned=True
162
+ )
@@ -0,0 +1,68 @@
1
+ """Reserve supplied instructions, active keyed facts, the question and tool schemas."""
2
+
3
+ from dataclasses import dataclass, replace
4
+ from datetime import datetime
5
+
6
+ from ..budget import plan_budget
7
+ from ..config import BudgetConfig
8
+ from ..context import ContextCarrier, LayerDiagnostic
9
+ from ..models import BlockKind, ContextBlock, ToolDefinition, aware_time, canonical_json, text_value
10
+ from ..tokens import TokenCounter
11
+ from .common import input_digest, render_request
12
+
13
+
14
+ @dataclass(frozen=True)
15
+ class PinLayer:
16
+ counter: TokenCounter
17
+ budget: BudgetConfig
18
+ system: str
19
+ at: datetime
20
+ tools: tuple[ToolDefinition, ...] = ()
21
+
22
+ def __post_init__(self) -> None:
23
+ text_value("system", self.system)
24
+ aware_time("at", self.at)
25
+ object.__setattr__(self, "tools", tuple(self.tools))
26
+
27
+ def apply(self, context: ContextCarrier) -> ContextCarrier:
28
+ active = tuple(pin for pin in context.pins.pins if pin.is_active(self.at))
29
+ content = (
30
+ canonical_json([{"key": pin.key, "value": pin.value} for pin in active])
31
+ if active
32
+ else ""
33
+ )
34
+ blocks = (
35
+ ContextBlock(
36
+ BlockKind.SYSTEM,
37
+ self.system,
38
+ prefix_stable=True,
39
+ text_tokens=self.counter.count_text(self.system),
40
+ ),
41
+ ContextBlock(
42
+ BlockKind.PINNED,
43
+ content,
44
+ sources=tuple(pin.source for pin in active if pin.source is not None),
45
+ prefix_stable=True,
46
+ text_tokens=self.counter.count_text(content),
47
+ ),
48
+ )
49
+ request = render_request(blocks, context.question, self.tools)
50
+ plan = plan_budget(required_request=request, config=self.budget, counter=self.counter)
51
+ # Re-running PIN deliberately invalidates all query-dependent layer outputs.
52
+ return replace(
53
+ context,
54
+ plan=plan,
55
+ blocks=blocks,
56
+ required_request=request,
57
+ prepared_fingerprint=input_digest(context),
58
+ window_planned=False,
59
+ retrieved_chunks=(),
60
+ diagnostics=context.diagnostics
61
+ + (
62
+ LayerDiagnostic(
63
+ "PIN",
64
+ "required_context_reserved",
65
+ output_text_tokens=sum(b.text_tokens for b in blocks),
66
+ ),
67
+ ),
68
+ )
@@ -0,0 +1,233 @@
1
+ """BM25+ over originals; optional recovery of missing chunks in capped WINDOW turns."""
2
+
3
+ import hashlib
4
+ import re
5
+ import unicodedata
6
+ from dataclasses import asdict, dataclass, replace
7
+
8
+ from rank_bm25 import BM25Plus
9
+
10
+ from ..config import RetrievalConfig
11
+ from ..context import ContextCarrier, LayerDiagnostic
12
+ from ..errors import MemoryIntegrityError
13
+ from ..models import BlockKind, Chunk, ContextBlock, SourceRef, Turn, canonical_json
14
+ from ..tokens import TokenCounter
15
+ from ..work import Cancellation, raise_if_cancelled, validate_cancellation
16
+ from .common import fits_block, history_digest, plan_window, set_block
17
+
18
+
19
+ @dataclass(frozen=True)
20
+ class ChunkIndex:
21
+ history_fingerprint: str
22
+ config_fingerprint: str
23
+ chunks: tuple[Chunk, ...]
24
+
25
+ def validate(self, history, config):
26
+ expected = hashlib.sha256(canonical_json(asdict(config)).encode()).hexdigest()
27
+ if (
28
+ self.history_fingerprint != history_digest(history)
29
+ or self.config_fingerprint != expected
30
+ ):
31
+ raise MemoryIntegrityError("Chunk index history or configuration is stale")
32
+ originals = {t.turn_id: t for t in history}
33
+ if (
34
+ not isinstance(self.chunks, tuple)
35
+ or any(not isinstance(c, Chunk) for c in self.chunks)
36
+ or len({c.chunk_id for c in self.chunks}) != len(self.chunks)
37
+ ):
38
+ raise MemoryIntegrityError("Invalid chunk index entries")
39
+ for chunk in self.chunks:
40
+ if not isinstance(chunk, Chunk) or chunk.source.turn_id not in originals:
41
+ raise MemoryIntegrityError("Chunk index contains unknown source")
42
+ chunk.validate_source(originals[chunk.source.turn_id])
43
+
44
+
45
+ def lexical_tokens(text: str) -> list[str]:
46
+ normalized = unicodedata.normalize("NFKC", text).casefold()
47
+ return re.findall(r"\w+(?:[-.]\w+)*", normalized)
48
+
49
+
50
+ def chunk_turns(
51
+ turns: tuple[Turn, ...], config: RetrievalConfig, *, cancellation: Cancellation | None = None
52
+ ) -> tuple[Chunk, ...]:
53
+ validate_cancellation(cancellation)
54
+ raise_if_cancelled(cancellation)
55
+ chunks = []
56
+ version = f"characters-v1:{config.chunk_characters}:{config.overlap_characters}"
57
+ stride = config.chunk_characters - config.overlap_characters
58
+ for turn in turns:
59
+ raise_if_cancelled(cancellation)
60
+ for message in turn.messages:
61
+ raise_if_cancelled(cancellation)
62
+ message_hash = message.content_hash
63
+ for start in range(0, len(message.content), stride):
64
+ raise_if_cancelled(cancellation)
65
+ end = min(len(message.content), start + config.chunk_characters)
66
+ content = message.content[start:end]
67
+ if content.strip():
68
+ source = SourceRef(
69
+ turn.scope,
70
+ turn.turn_id,
71
+ message.message_id,
72
+ turn.revision,
73
+ start,
74
+ end,
75
+ message_hash,
76
+ )
77
+ identifier = hashlib.sha256(
78
+ canonical_json([asdict(source), version]).encode()
79
+ ).hexdigest()
80
+ chunks.append(Chunk(identifier, source, content, version))
81
+ if end == len(message.content):
82
+ break
83
+ return tuple(chunks)
84
+
85
+
86
+ def evidence_block(chunks: tuple[Chunk, ...], counter: TokenCounter) -> ContextBlock:
87
+ content = (
88
+ canonical_json(
89
+ [
90
+ {
91
+ "turn": c.source.turn_id,
92
+ "message": c.source.message_id,
93
+ "start": c.source.start,
94
+ "end": c.source.end,
95
+ "text": c.content,
96
+ }
97
+ for c in chunks
98
+ ]
99
+ )
100
+ if chunks
101
+ else ""
102
+ )
103
+ return ContextBlock(
104
+ BlockKind.RETRIEVED,
105
+ content,
106
+ sources=tuple(c.source for c in chunks),
107
+ text_tokens=counter.count_text(content),
108
+ )
109
+
110
+
111
+ def overlaps(left: Chunk, right: Chunk) -> bool:
112
+ a, b = left.source, right.source
113
+ return (a.scope, a.turn_id, a.message_id, a.revision) == (
114
+ b.scope,
115
+ b.turn_id,
116
+ b.message_id,
117
+ b.revision,
118
+ ) and max(a.start, b.start) < min(a.end, b.end)
119
+
120
+
121
+ def window_messages(context: ContextCarrier) -> dict[tuple[str, str], str]:
122
+ """Actual working content, not full-original provenance ranges on WINDOW blocks."""
123
+ return {
124
+ (turn.turn_id, message.message_id): message.content
125
+ for turn in context.working_turns
126
+ if turn.turn_id in context.plan.window_turn_ids
127
+ for message in turn.messages
128
+ }
129
+
130
+
131
+ def represented_in_window(chunk: Chunk, visible: dict[tuple[str, str], str]) -> bool:
132
+ text = visible.get((chunk.source.turn_id, chunk.source.message_id))
133
+ return text is not None and chunk.content in text
134
+
135
+
136
+ @dataclass(frozen=True)
137
+ class RetrieveLayer:
138
+ counter: TokenCounter
139
+ config: RetrievalConfig = RetrievalConfig()
140
+ chunk_index: ChunkIndex | None = None
141
+
142
+ def apply(self, context: ContextCarrier) -> ContextCarrier:
143
+ if self.chunk_index is not None:
144
+ if not isinstance(self.chunk_index, ChunkIndex):
145
+ raise MemoryIntegrityError("Expected a validated chunk index")
146
+ self.chunk_index.validate(context.original_turns, self.config)
147
+ planned = plan_window(context, self.counter)
148
+ visible = window_messages(planned)
149
+ old = tuple(
150
+ t
151
+ for t in planned.original_turns
152
+ if t.turn_id not in planned.plan.window_turn_ids
153
+ or (
154
+ self.config.recover_capped_window
155
+ and any(m.content != visible[(t.turn_id, m.message_id)] for m in t.messages)
156
+ )
157
+ )
158
+ query = sorted(set(lexical_tokens(planned.question.content)))
159
+ selected: tuple[Chunk, ...] = ()
160
+ reason = "no_match"
161
+ if not query:
162
+ reason = "empty_query"
163
+ elif not old:
164
+ reason = "no_old_history"
165
+ elif planned.plan.retrieval_tokens == 0:
166
+ reason = "zero_reserve"
167
+ else:
168
+ if self.chunk_index is None:
169
+ # Preserve the exact 0.8.0 call form when no token is supplied.
170
+ chunks = (
171
+ chunk_turns(old, self.config)
172
+ if context.cancellation is None
173
+ else chunk_turns(old, self.config, cancellation=context.cancellation)
174
+ )
175
+ else:
176
+ ids = {t.turn_id for t in old}
177
+ chunks = tuple(c for c in self.chunk_index.chunks if c.source.turn_id in ids)
178
+ searchable = []
179
+ for chunk in chunks:
180
+ raise_if_cancelled(context.cancellation)
181
+ if represented_in_window(chunk, visible):
182
+ continue
183
+ terms = lexical_tokens(chunk.content)
184
+ if terms:
185
+ searchable.append((chunk, terms))
186
+ if searchable:
187
+ # BM25+ has positive IDF for one-document/small corpora. Its delta
188
+ # also scores nonmatches: explicitly require lexical intersection.
189
+ ranker = BM25Plus(
190
+ [terms for _, terms in searchable],
191
+ k1=self.config.k1,
192
+ b=self.config.b,
193
+ delta=1.0,
194
+ )
195
+ scores = ranker.get_scores(query)
196
+ raise_if_cancelled(context.cancellation)
197
+ ranked = [
198
+ (float(score), c)
199
+ for (c, terms), score in zip(searchable, scores, strict=True)
200
+ if set(query).intersection(terms) and score > self.config.minimum_score
201
+ ]
202
+ ranked.sort(key=lambda item: (-item[0], item[1].chunk_id))
203
+ raise_if_cancelled(context.cancellation)
204
+ if ranked:
205
+ reason = "evidence_over_budget"
206
+ for _, chunk in ranked:
207
+ raise_if_cancelled(context.cancellation)
208
+ if any(overlaps(chunk, existing) for existing in selected):
209
+ continue
210
+ candidate = selected + (chunk,)
211
+ if fits_block(
212
+ planned,
213
+ evidence_block(candidate, self.counter),
214
+ planned.plan.retrieval_tokens,
215
+ self.counter,
216
+ ):
217
+ selected = candidate
218
+ if len(selected) >= self.config.top_k:
219
+ break
220
+ if selected:
221
+ reason = "evidence_retrieved"
222
+ block = evidence_block(selected, self.counter)
223
+ result = set_block(
224
+ planned,
225
+ block,
226
+ LayerDiagnostic(
227
+ "RETRIEVE",
228
+ reason,
229
+ tuple(dict.fromkeys(c.source.turn_id for c in selected)),
230
+ output_text_tokens=block.text_tokens,
231
+ ),
232
+ )
233
+ return replace(result, retrieved_chunks=selected)