464 lines
19 KiB
Markdown
464 lines
19 KiB
Markdown
# LLM And Stage Reference Expansion Implementation Plan
|
|
|
|
## Purpose
|
|
|
|
Implement these future roadmap items:
|
|
|
|
- First-class LLM access for all LLM-eligible pipeline stages: `chunk`,
|
|
`extract`, and `normalize`.
|
|
- Expand reference support beyond extractors to `chunk` and `normalize`
|
|
modules, using the same declared-slot model and preserving stage-specific
|
|
ownership boundaries.
|
|
|
|
This is a decision-complete implementation plan for an LLM coding agent. Follow
|
|
the stages in order. Keep current-behavior docs unchanged until the
|
|
corresponding behavior exists. Do not implement token budgeting, non-file
|
|
reference producers, structured references, retrieval, or multiple effective LLM
|
|
profiles in this pass.
|
|
|
|
## Target Behavior
|
|
|
|
LLM access:
|
|
|
|
- `contracts.ChunkRequest`, `contracts.ExtractionRequest`, and
|
|
`contracts.NormalizeRequest` all carry `LLMClient`, `LLMProfile`, `Options`,
|
|
and `Metadata`.
|
|
- The runner passes the configured structured LLM client to chunkers,
|
|
extractors, and normalizers.
|
|
- The existing single-effective-LLM-profile CLI restriction remains in place.
|
|
|
|
References:
|
|
|
|
- `chunk`, `extract`, and `normalize` modules can declare reference slots.
|
|
- The framework treats references as opaque UTF-8 text plus provenance. Stage
|
|
modules own slot names and semantics.
|
|
- Reference slots are declared in both runtime module contracts and
|
|
`ModuleSpec.ReferenceSlots`, so config and pipeline validation can inspect
|
|
slots without constructing modules.
|
|
- `ModuleSpec.ReferenceSlots` is valid only for `chunk`, `extract`, and
|
|
`normalize` modules. `input`, `merge`, `validate`, and `output` modules must
|
|
not declare reference slots.
|
|
- Pipeline-level `references` remain defaults. A pipeline-level binding is
|
|
valid when any declared eligible module in the pipeline declares the slot. In
|
|
a run, it is applied only to selected targets that declare the slot.
|
|
- Stage-local reference bindings override pipeline-level defaults for that
|
|
target.
|
|
- CLI `--reference` and `--without-reference` can target chunk, extract, and
|
|
normalize references explicitly, while preserving existing unambiguous flat
|
|
forms.
|
|
- Reference materialization still accepts file producers only, validates UTF-8,
|
|
infers media types from extensions, enforces optional `AcceptedMediaTypes` and
|
|
`MaxBytes`, warns for empty files, and never writes content to diagnostics,
|
|
logs, errors, or manifests.
|
|
- Manifest and diagnostics provenance identifies the reference target stage and,
|
|
for lane-owned stages, the lane ID.
|
|
|
|
## Target Config Shape
|
|
|
|
Keep existing config compatibility and add stage-specific binding locations:
|
|
|
|
```yaml
|
|
pipelines:
|
|
dnd-session:
|
|
input: seriatim
|
|
references:
|
|
campaign_context: ./campaign/context.md
|
|
chunk:
|
|
module: dnd/scenes
|
|
references:
|
|
scene_guide: ./campaign/scenes.md
|
|
artifacts:
|
|
spells:
|
|
extract: dnd/spells
|
|
references:
|
|
glossary: ./campaign/glossary.md
|
|
normalize:
|
|
module: noop
|
|
references:
|
|
normalization_notes: ./campaign/normalization.md
|
|
```
|
|
|
|
Compatibility rule:
|
|
|
|
- Existing `artifacts.<lane>.references` remains supported as an alias for
|
|
extractor references.
|
|
- New `artifacts.<lane>.extract.references` is the canonical extract-stage
|
|
location.
|
|
- If both the legacy lane-level `references` map and
|
|
`extract.references` bind the same slot, `extract.references` wins. This keeps
|
|
migration deterministic and avoids breaking existing configs.
|
|
|
|
`chunk.references` and `normalize.references` are supported only when their
|
|
module binding uses object form. Scalar bindings continue to work when no
|
|
stage-local references are needed.
|
|
|
|
## Target CLI Reference Selectors
|
|
|
|
Preserve existing valid forms and add explicit stage selectors:
|
|
|
|
- `slot=path`: valid only when exactly one selected reference target across
|
|
chunk, selected extractors, and selected normalizers declares `slot`.
|
|
- `chunk.slot=path`: targets the singleton chunk module.
|
|
- `lane.slot=path`: valid only when exactly one selected module in that lane,
|
|
extract or normalize, declares `slot`.
|
|
- `lane.extract.slot=path`: targets a lane extractor.
|
|
- `lane.normalize.slot=path`: targets a lane normalizer.
|
|
|
|
`--without-reference` accepts the same selector forms without `=path`.
|
|
|
|
Ambiguous selectors must fail before any LLM call and should name the ambiguous
|
|
targets and the more specific selector forms.
|
|
|
|
## Stage 1: Contract And Registry Expansion
|
|
|
|
Add reference declaration support to chunkers and normalizers and LLM delivery
|
|
to normalizers. This stage should not change config shape or runtime reference
|
|
resolution yet.
|
|
|
|
Implementation steps:
|
|
|
|
- Add `References contracts.ReferenceSet` to `contracts.ChunkRequest`.
|
|
- Add `LLMClient contracts.StructuredLLMClient` and
|
|
`References contracts.ReferenceSet` to `contracts.NormalizeRequest`.
|
|
- Add `ReferenceSlots() []ReferenceSlot` to `contracts.Chunker` and
|
|
`contracts.Normalizer`.
|
|
- Update every concrete chunker and normalizer plus all test fakes to implement
|
|
`ReferenceSlots()`. Existing modules without references should return `nil`.
|
|
- Change `validateModuleSpec` so `ModuleSpec.ReferenceSlots` is allowed for
|
|
`StageChunk`, `StageExtract`, and `StageNormalize`, and rejected for all other
|
|
stages.
|
|
- Add registry/module tests proving:
|
|
- chunker specs may declare valid reference slots;
|
|
- normalizer specs may declare valid reference slots;
|
|
- extractor specs still may declare valid reference slots;
|
|
- input, merge, validate, and output specs reject reference slots;
|
|
- invalid slot names, duplicate slots, and negative `MaxBytes` are rejected
|
|
for all eligible stages.
|
|
- Add production registration tests comparing runtime `ReferenceSlots()` with
|
|
registered `ModuleSpec.ReferenceSlots` for chunkers, extractors, and
|
|
normalizers that declare slots. Modules with no slots should be covered by
|
|
simple nil/empty assertions.
|
|
|
|
Verification:
|
|
|
|
- `go test ./internal/framework/contracts`
|
|
- `go test ./internal/framework/pipeline`
|
|
- `go test ./internal/modules/chunk/...`
|
|
- `go test ./internal/modules/normalize/...`
|
|
- `go test ./...`
|
|
|
|
## Stage 2: Resolved Reference Target Model
|
|
|
|
Replace extractor-only resolved reference storage with a target-aware model.
|
|
Keep external config and CLI behavior unchanged in this stage where possible.
|
|
|
|
Implementation steps:
|
|
|
|
- Introduce a target model in `internal/framework/pipeline`, for example:
|
|
- target stage: `chunk`, `extract`, or `normalize`;
|
|
- optional lane ID for lane-owned targets;
|
|
- module key;
|
|
- reference bindings;
|
|
- materialized `ReferenceSet`.
|
|
- Add a resolved chunk reference holder to `ResolvedPipeline`.
|
|
- Split lane reference storage into extract and normalize holders on
|
|
`ResolvedArtifactLane`.
|
|
- Keep JSON field names deterministic and redaction-friendly. Do not expose
|
|
reference content through resolved pipeline JSON.
|
|
- Update `CloneReferenceSet`, `ReferenceProvenance`, redacted effective config,
|
|
resolved-pipeline diagnostics, and any affected tests for the target-aware
|
|
model.
|
|
- Preserve existing extractor behavior by mapping old lane extractor references
|
|
into the new extract target.
|
|
- Keep the pipeline digest deterministic and based on resolved reference
|
|
bindings, not materialized reference bytes. Materialized reference digests
|
|
remain manifest provenance.
|
|
|
|
Verification:
|
|
|
|
- `go test ./internal/framework/pipeline`
|
|
- `go test ./internal/core/config`
|
|
- `go test ./internal/core/diagnostics`
|
|
- `go test ./...`
|
|
|
|
## Stage 3: Config Parsing And Validation
|
|
|
|
Add stage-local reference fields while preserving existing configs.
|
|
|
|
Implementation steps:
|
|
|
|
- Extend the file config module binding object to accept `references`.
|
|
- Add `References map[string]string` to `pipeline.ModuleBinding`, or use a
|
|
narrower equivalent if the implementation can do so without duplicating
|
|
binding parsing. If added to `ModuleBinding`, validation must reject
|
|
references on non-eligible stages.
|
|
- Apply file config references from:
|
|
- `pipelines.<id>.chunk.references`;
|
|
- `pipelines.<id>.artifacts.<lane>.extract.references`;
|
|
- `pipelines.<id>.artifacts.<lane>.normalize.references`;
|
|
- existing `pipelines.<id>.artifacts.<lane>.references` as an extract-stage
|
|
compatibility alias.
|
|
- Validate reference maps for empty names, duplicate-after-trim names, and empty
|
|
sources at each new location.
|
|
- Reject references on `input`, `merge`, validators, and `output` bindings with
|
|
clear config errors.
|
|
- Preserve existing `llm_profile` validation for chunk, extract, and normalize
|
|
bindings. No new LLM profile semantics are introduced in this pass.
|
|
- Add config tests for:
|
|
- parsing `chunk.references`;
|
|
- parsing `extract.references`;
|
|
- parsing `normalize.references`;
|
|
- legacy lane `references` compatibility;
|
|
- duplicate conflicts where `extract.references` overrides legacy lane
|
|
references for the same slot;
|
|
- rejection of references on unsupported module binding locations;
|
|
- redaction/effective config cloning for all new reference maps.
|
|
|
|
Verification:
|
|
|
|
- `go test ./internal/core/config`
|
|
- `go test ./internal/framework/pipeline`
|
|
- `go test ./...`
|
|
|
|
## Stage 4: Pipeline Resolution
|
|
|
|
Resolve reference bindings against all eligible stage targets and selected lanes
|
|
without constructing modules.
|
|
|
|
Implementation steps:
|
|
|
|
- During `ResolvePipeline`, collect declared reference slots from:
|
|
- the configured chunker spec;
|
|
- every declared artifact lane extractor spec;
|
|
- every declared artifact lane normalizer spec.
|
|
- Keep pipeline-level reference validation broad: a pipeline-level binding is
|
|
valid if any declared eligible target in the full pipeline declares that slot,
|
|
even when `--only` selects lanes that do not consume it.
|
|
- During a selected run, apply pipeline-level defaults only to:
|
|
- the singleton chunk target if its chunker declares the slot;
|
|
- selected extractor targets that declare the slot;
|
|
- selected normalizer targets that declare the slot.
|
|
- Apply stage-local references after pipeline defaults:
|
|
- chunk-local references override chunk pipeline defaults;
|
|
- extract-local references override extract pipeline defaults;
|
|
- normalize-local references override normalize pipeline defaults.
|
|
- Enforce required slots independently per selected target after defaults,
|
|
local references, CLI overrides, and CLI unbinds.
|
|
- Keep lane-local strictness: extract-local references must be declared by that
|
|
lane's extractor, and normalize-local references must be declared by that
|
|
lane's normalizer.
|
|
- Return clear errors that name pipeline ID, stage, lane ID when present, slot,
|
|
and module key.
|
|
- Add pipeline tests for:
|
|
- pipeline default consumed by chunk only;
|
|
- pipeline default consumed by extract only;
|
|
- pipeline default consumed by normalize only;
|
|
- one pipeline default consumed by several eligible targets;
|
|
- valid pipeline default declared only by an unselected lane target;
|
|
- unknown pipeline-level slot rejected when no declared target has it;
|
|
- extract-local reference rejected by normalizer-only slot;
|
|
- normalize-local reference rejected by extractor-only slot;
|
|
- required chunk reference missing;
|
|
- required normalize reference missing;
|
|
- local overrides over pipeline defaults for each eligible stage.
|
|
|
|
Verification:
|
|
|
|
- `go test ./internal/framework/pipeline`
|
|
- `go test ./internal/core/config`
|
|
- `go test ./...`
|
|
|
|
## Stage 5: CLI Reference Selectors
|
|
|
|
Extend CLI reference override and unbind resolution to target chunk, extract,
|
|
and normalize references.
|
|
|
|
Implementation steps:
|
|
|
|
- Replace extractor-only CLI reference target lookup with target-aware lookup
|
|
over the resolved eligible targets for the selected run.
|
|
- Support selector forms listed in "Target CLI Reference Selectors".
|
|
- Preserve existing behavior for unambiguous `slot` and `lane.slot` forms.
|
|
- Reject ambiguous `slot` and `lane.slot` forms with a message naming matching
|
|
targets and suggesting `chunk.slot`, `lane.extract.slot`, or
|
|
`lane.normalize.slot` as appropriate.
|
|
- Reject selectors that target an unselected lane during `--only` runs.
|
|
- Reject selectors for stages that do not declare the slot.
|
|
- Keep `--reference` empty-path rejection and `--without-reference` optional
|
|
unbinding semantics.
|
|
- Add CLI tests for:
|
|
- `chunk.slot=path`;
|
|
- `lane.extract.slot=path`;
|
|
- `lane.normalize.slot=path`;
|
|
- flat slot resolution across exactly one target;
|
|
- lane slot resolution across exactly one lane-local target;
|
|
- ambiguous flat slot across chunk and extract;
|
|
- ambiguous lane slot across extract and normalize;
|
|
- unbind forms for chunk, extract, and normalize;
|
|
- unbind leaving required chunk or normalize slot missing;
|
|
- `--only` with an unselected lane selector.
|
|
|
|
Verification:
|
|
|
|
- `go test ./internal/cli`
|
|
- `go test ./internal/framework/pipeline`
|
|
- `go test ./...`
|
|
|
|
## Stage 6: Materialization And Provenance
|
|
|
|
Materialize references for chunk, extract, and normalize targets with one shared
|
|
implementation.
|
|
|
|
Implementation steps:
|
|
|
|
- Refactor materialization so it accepts a resolved target plus that target's
|
|
declared slots, rather than assuming an extractor lane.
|
|
- Reuse the existing file behavior:
|
|
- config paths relative to the config file;
|
|
- CLI paths relative to the current working directory;
|
|
- UTF-8 validation for all files;
|
|
- extension-based media type inference;
|
|
- optional `AcceptedMediaTypes` enforcement;
|
|
- optional `MaxBytes` enforcement;
|
|
- empty-file warning;
|
|
- `sha256:` content digest.
|
|
- Store materialized reference sets on the resolved chunk target, extract
|
|
targets, and normalize targets.
|
|
- Update warnings to include target context in `Scope`, such as:
|
|
- `pipeline.<id>.chunk.reference.<slot>`;
|
|
- `pipeline.<id>.lane.<lane>.extract.reference.<slot>`;
|
|
- `pipeline.<id>.lane.<lane>.normalize.reference.<slot>`.
|
|
- Update manifest and diagnostics provenance to include:
|
|
- `stage`: `chunk`, `extract`, or `normalize`;
|
|
- `lane_id`: omitted for chunk and present for extract/normalize;
|
|
- `slot_name`;
|
|
- origin type and URI;
|
|
- digest;
|
|
- media type;
|
|
- size bytes;
|
|
- binding source.
|
|
- Keep reference content out of manifests, diagnostics, logs, and errors.
|
|
- Add tests for materialization and provenance for all three eligible stages,
|
|
including media-type rejection and UTF-8 rejection on non-extractor targets.
|
|
|
|
Verification:
|
|
|
|
- `go test ./internal/framework/pipeline`
|
|
- `go test ./internal/core/artifacts`
|
|
- `go test ./internal/core/diagnostics`
|
|
- `go test ./internal/modules/output/json`
|
|
- `go test ./internal/cli`
|
|
- `go test ./...`
|
|
|
|
## Stage 7: Runner Delivery
|
|
|
|
Deliver LLM clients and references to eligible stage modules at runtime.
|
|
|
|
Implementation steps:
|
|
|
|
- Pass `CloneReferenceSet` of the materialized chunk reference set into
|
|
`contracts.ChunkRequest.References`.
|
|
- Continue passing `LLMClient` into `contracts.ChunkRequest`.
|
|
- Continue passing extract references and `LLMClient` into
|
|
`contracts.ExtractionRequest`.
|
|
- Pass `LLMClient` and `CloneReferenceSet` of the materialized normalize
|
|
reference set into `contracts.NormalizeRequest`.
|
|
- Ensure normalizers receive their configured `LLMProfile`, options, and
|
|
metadata as they do today.
|
|
- Add runner tests proving:
|
|
- chunkers receive LLM client and chunk references;
|
|
- extractors still receive LLM client and extract references;
|
|
- normalizers receive LLM client and normalize references;
|
|
- reference sets are cloned before delivery so modules cannot mutate resolved
|
|
pipeline state;
|
|
- missing LLM client still fails only when a module actually uses it, matching
|
|
existing chunk/extract behavior.
|
|
- Update the walking skeleton to exercise LLM-backed normalize access if a small
|
|
fake normalizer can do so without making the fixture noisy.
|
|
|
|
Verification:
|
|
|
|
- `go test ./internal/framework/pipeline`
|
|
- `go test ./...`
|
|
|
|
## Stage 8: Prompt Helpers And Module Usage
|
|
|
|
Make prompt reference helpers usable by chunk and normalize modules, then update
|
|
production modules only where needed.
|
|
|
|
Implementation steps:
|
|
|
|
- Confirm `internal/framework/prompt` reference rendering is contract-based and
|
|
not extractor-specific. If any naming or error text says extractor-only,
|
|
generalize it to module/stage language.
|
|
- Add prompt tests rendering references for a fake chunker or normalizer prompt
|
|
bundle if existing tests cover only extractor usage.
|
|
- Update production chunkers and normalizers:
|
|
- `generic` chunker: no reference slots.
|
|
- `dnd/scenes` chunker: no reference slots in this pass unless a clear
|
|
module-owned slot already exists.
|
|
- `noop` normalizer: no reference slots.
|
|
- Do not invent a new production normalize module in this pass. The goal is
|
|
framework support, not a domain normalizer.
|
|
|
|
Verification:
|
|
|
|
- `go test ./internal/framework/prompt`
|
|
- `go test ./internal/modules/chunk/...`
|
|
- `go test ./internal/modules/normalize/...`
|
|
- `go test ./...`
|
|
|
|
## Stage 9: Documentation And Examples
|
|
|
|
After behavior is implemented and tested, update canonical docs and examples.
|
|
|
|
Implementation steps:
|
|
|
|
- Update `docs/cli.md` with the expanded `--reference` and
|
|
`--without-reference` selector grammar and examples.
|
|
- Update `docs/config.md` with `chunk.references`,
|
|
`artifacts.<lane>.extract.references`, `artifacts.<lane>.normalize.references`,
|
|
and the legacy `artifacts.<lane>.references` compatibility rule.
|
|
- Update `docs/internal/modules.md` with module-author guidance for
|
|
`ReferenceSlots()` on chunkers, extractors, and normalizers, plus LLM access
|
|
expectations for chunk, extract, and normalize.
|
|
- Update `docs/internal/pipeline.md` with target-aware reference resolution,
|
|
materialization, provenance, and runtime delivery.
|
|
- Update `docs/integrations/json-output.md` with the new reference provenance
|
|
shape including `stage` and optional `lane_id`.
|
|
- Update `docs/troubleshooting.md` with ambiguous selector examples for chunk,
|
|
extract, and normalize targets.
|
|
- Update maintained examples only if they can remain small and validated. Do
|
|
not add speculative reference files for chunk or normalize modules without a
|
|
production module that actually declares those slots.
|
|
- Update `docs/roadmap/future.md` by removing or revising the two completed
|
|
items while leaving still-deferred work such as token budgeting and non-file
|
|
producers.
|
|
- Replace this implementation plan with a concise completed-note document after
|
|
implementation is finished.
|
|
|
|
Verification:
|
|
|
|
- `rg -n "chunk.references|normalize.references|lane.extract.slot|lane.normalize.slot" docs/cli.md docs/config.md docs/internal docs/integrations docs/troubleshooting.md`
|
|
- `go test ./...`
|
|
- `go vet ./...`
|
|
- `go build ./cmd/notarius`
|
|
|
|
## Final Acceptance Criteria
|
|
|
|
The feature is complete when:
|
|
|
|
- chunkers, extractors, and normalizers can declare reference slots through
|
|
runtime contracts and registry specs;
|
|
- normalizers receive the structured LLM client at runtime;
|
|
- config can bind references at pipeline, chunk, extract, and normalize levels;
|
|
- CLI reference overrides and unbinds can target chunk, extract, and normalize
|
|
references without ambiguous selector behavior;
|
|
- pipeline resolution validates all reference bindings before any LLM call;
|
|
- materialization and provenance work uniformly for chunk, extract, and
|
|
normalize references;
|
|
- reference manifest entries identify stage and lane context;
|
|
- existing extractor reference behavior remains backward compatible;
|
|
- canonical docs describe only implemented behavior;
|
|
- `go test ./...`, `go vet ./...`, and `go build ./cmd/notarius` pass.
|