Skip to content

Python context layer: fold source prose into retrieval - #285

Open
jat255 wants to merge 2 commits into
jat255/context-layer-dictionary-chunksfrom
jat255/context-layer-augment
Open

Python context layer: fold source prose into retrieval#285
jat255 wants to merge 2 commits into
jat255/context-layer-dictionary-chunksfrom
jat255/context-layer-augment

Conversation

@jat255

@jat255 jat255 commented Sep 4, 2026

Copy link
Copy Markdown
Collaborator

Last of four PRs for M4, the Python context layer (kata c8a0). Stacked on #284, which adds the chunks this folds in.

augment_context_layer(layer, sources) appends each source's dictionary chunks and returns a new layer, so a dictionary alone is searchable context even when the caller passed no context files. It is internal, as it is in pkg-r; the agent constructor in M5 is what calls it.

The returned layer is always a new object with a fresh index. Source enrichment belongs to the agent that owns the sources, so mutating the caller's layer would leak one agent's sources into the next agent built from the same layer. With nothing to add the argument comes back unchanged, None included, so an agent with neither context files nor dictionary prose gets no layer rather than an empty one.

The milestone's acceptance criterion is a test rather than a one-off script, asserting equality rather than a match, so it fails if a glossary term ever comes back as a fragment instead of whole.

One deliberate gap. pkg-r also folds in each source's warehouse semantic models here, the Snowflake semantic views and Databricks metric views. This package has no surface for those yet (kata gcgj covers them, and notes that porting the reader alone would produce records nothing consumes), so that half is a comment at the point where it will go rather than code. The milestone plan attributed those models to M3, which was wrong: M3's semantic layer is measures written in Python, and these are the warehouse's own definitions.

R changes

Test-only, no change to any function under pkg-r/R/.

The behaviour this PR adds on the Python side already existed in R, and each package was checking it separately. tests/shared/context_layer.json gains an augment_context_layer section and test-context-layer.R gains a runner for it. Each case lists one entry per source, so the cases cover where the sources' chunks land relative to the caller's own documents, that every source contributes in the order given, and that a source carrying no dictionary is skipped rather than counted. An absent layer, an empty layer, and a dictionary whose prose is all empty are separate cases, because they produce separate outcomes.

Two hand-written tests in test-data-dictionary.R were deleted, and the fixture runner replaces both. "augmenting keeps existing context docs" asserted that one known document survived and that the document count grew, which the runner now pins as an exact expected vector. "augmenting without dictionaries is a no-op" asserted expect_null(augment_context_layer(NULL, list(test_source()))), which is the runner's "no sources and no layer stays absent" case plus the stronger "a source with no dictionary leaves the layer alone". A comment at the deletion site points at the fixture, so the next reader looking for that coverage finds it. "dictionary prose is searchable via the context layer" is kept and untouched: it goes through BM25 retrieval, and ranking is engine-specific rather than a cross-language contract.

Whether the returned layer is a new object carrying a fresh index is asserted in the Python suite only, and the fixture says so. It is what keeps one agent's sources out of another's, but it is not observable text, and R holds its documents in an R6 object where the equivalent assertion would be about object identity rather than behaviour.

Why it is safe: no R source file changed, so R behaviour cannot have changed. What needs checking is that the runner is not vacuous, and I checked that rather than trusting the pass. The case list is guarded with expect_gt(length(cases), 0). I confirmed separately that the three-source case really constructs three sources and that its middle, dictionary-less source is skipped, because a runner that silently built one source would still have passed the other cases. test-context-layer.R is at 33 passing, and test-data-dictionary.R (68 passing, 3 skipped on CRAN), test-definitions.R (114 passing) and test-citations.R (84 passing) were run for the deletions and the other augment_context_layer() callers. Not the full R suite, for the #268 hang.

pkg-r/tests/testthat/fixtures/shared/context_layer.json is generated by scripts/sync-shared-fixtures.sh and needs no review.

An agent's sources describe themselves, and that prose is worth
retrieving. augment_context_layer() appends each source's dictionary
chunks and hands back a new layer, so a dictionary alone is searchable
context even when the caller passed no context files at all.

The new layer is always a new object. Source enrichment belongs to the
agent that owns the sources; mutating the caller's layer would leak one
agent's sources into the next agent built from the same layer. With
nothing to add the argument comes back unchanged, None included, so an
agent with neither context nor a dictionary has no layer rather than an
empty one.

The milestone's acceptance check is a test rather than a one-off script,
and asserts equality rather than a match: the glossary term comes back
whole, not as a fragment.

pkg-r also folds in a warehouse's own semantic models here. This package
has no surface for those yet, so that half is a comment at the point
where it will go.
Both packages fold a source's dictionary prose into an agent's context
layer, and each was checking that on its own. Add an
augment_context_layer section to the shared context_layer fixture and a
runner in each suite.

Each case lists one entry per source, so the cases cover where the
sources' chunks land relative to the caller's own documents, that every
source contributes in the order given, and that a source carrying no
dictionary is skipped rather than counted. An absent layer, an empty
layer, and a dictionary whose prose is all empty are separate cases,
because they are separate outcomes.

Whether the returned layer is a new object with a fresh index stays out
of the fixture and out of R. It is what keeps one agent's sources from
leaking into the next, but it is not observable text, so it is asserted
in the Python suite only.

Two R tests in test-data-dictionary.R covered the same ground more
loosely, one asserting only that the document count grew. The fixture
runner replaces both.
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