Orchestrata Ultimate / Specs / 107-context-injection-improvements
107-context-injection-improvements: Context-Injection Improvements
The existing context assembler (src/orchestrata_ultimate/context/assembler.py) has the right layered shape, but several layers are either empty in production or under-used:
At a glance
| Item | Value |
|---|---|
| Spec directory | specs/107-context-injection-improvements/ |
| Status line in the spec | not stated |
| Requirement IDs defined | 73 |
With a @traces citation in src/ |
13 (17.8%) |
With a @traces citation in tests/ |
1 (1.4%) |
| Tracked files | 2 |
| RTM page | 107-context-injection-improvements |
Reading this spec
This spec improves how context reaches agents through the existing layered context assembler. The assembler has the right shape, but several layers are empty or under-used in production: session and goal context is implemented but never fed because production callers pass no context data, the plan layer is budgeted but never populated, memory recall loads an unbounded number of entries and scores them in Python, the semantic graph and the serialized AST are rich sources that are not injected automatically, context use per layer is not logged so tuning is blind, and the artifact store is in-memory only with no digest pagination.
Four ruling constraints apply. Selection is never LLM-driven; ranking and filtering stay deterministic, matching SPEC-106 FR-003. The UnifiedContextAssembler.assemble() signature is unchanged, and new layers are optional parts. Every new source has a no-op fallback when its backend (Neo4j, Memgraph, Redis or Prolog) is unavailable. Tests use real backends, without mocks, on production context paths.
The fourteen functional requirements (FR-001 to FR-014) are: session and plan context, bounded memory recall (for example a max_age_seconds pre-filter before similarity scoring), a semantic-graph context layer, an AST-derived context layer (a budgeted summary of the most relevant complex or called symbols), context telemetry, an artifact store refactor (digests ranked by recency and relevance, top-N within budget), on-demand context tools, context pack caching, expanded graph-context sections (requirements and decisions found through TRACES_TO and APPLIES_TO), runtime signal ingestion, a validator feedback loop, and snapshot and knowledge-base freshness (the ast-sync step runs incrementally, cutting a no-change run from 60.7 s to 8.3 s). The last two requirements concern delivery: claude/ in the repository is the source of truth and ~/.claude is a deployment of it, the deployed virtual environment is verified free-threaded with no reference to the source checkout, and shipping is mechanical. The 73 requirement IDs are numbered REQ-107-NNN.
The code touches many places, so each module holds only a few citations: context tools (6 requirements), the AST context, plan context and semantic context providers, the usage log, the artifact store and the context injector. The requirements traceability matrix lists each ID. The spec states no status line and tracks 2 files. Measured coverage is 13 of 73 requirements cited by at least one @traces in src/ (17.8%), spread over 9 modules; the module citing the most of them is orchestrata_ultimate/tools/context_tools (6 requirements). In tests/, 1 requirement is cited by a test-side @traces. 60 requirements have no citing symbol; the behaviour may still exist in code that was never annotated.
Requirements
Checkbox is the state recorded in the requirements checklist; Traced by is the count of src/ symbols whose @traces names the ID.
| ID | Done | Requirement | Traced by |
|---|---|---|---|
REQ-107-001 |
yes | Production PipelineRunner and gateway paths load ContextData from the active SessionController and pass it to UnifiedContextAssembler.assemble() |
none |
REQ-107-002 |
yes | mission_context.render_mission_context() is rendered into the mission layer when context_data is supplied |
none |
REQ-107-003 |
yes | A plan_context provider renders recent pipeline outcomes / next planned stages into the existing plan budget |
3 |
REQ-107-004 |
yes | An observed test proves the mission layer is non-empty when a session has active goals | none |
REQ-107-060 |
yes | PlanParser parses plans/execution-plan.md into structured (step_num, title, goal, actions, verify, expected) tuples |
8 |
REQ-107-061 |
yes | Goal persists a current_step index; the session controller provides advance_goal_step and set_goal_step |
none |
REQ-107-062 |
yes | plan_context provider uses current_step to render the current step in full plus next steps and recent done summaries |
none |
REQ-107-005 |
yes | MemoryEngine.search accepts a max_entries pre-filter before similarity scoring |
none |
REQ-107-006 |
yes | MemoryEngine.search accepts a max_age_seconds pre-filter before similarity scoring |
none |
REQ-107-007 |
yes | Default filters (max_entries=50, max_age_seconds=3600) keep memory recall bounded in UnifiedContextAssembler |
none |
REQ-107-008 |
yes | An observed test proves recall returns at most max_entries results |
none |
REQ-107-009 |
yes | SemanticGraphContextProvider queries Memgraph for chunks relevant to a query string |
none |
REQ-107-010 |
yes | The provider contributes a semantic part to ContextAssembly for subjects implement, fix_issues, and review_and_test |
none |
REQ-107-011 |
yes | When Memgraph is unavailable the provider returns an empty string without raising | none |
REQ-107-012 |
yes | An observed test proves semantic context is injected for implement subject |
none |
REQ-107-013 |
yes | AST complexity, branch, and call-graph signals are queryable from Neo4j or the AST snapshot | none |
REQ-107-014 |
yes | An ast_context provider returns a budgeted summary of the most relevant complex/called symbols |
1 |
REQ-107-015 |
yes | When the AST/KG source is unavailable the provider returns an empty string without raising | 4 |
REQ-107-016 |
yes | An observed test proves ast_context is non-empty for a task targeting a known module |
none |
REQ-107-020 |
yes | Every UnifiedContextAssembler.assemble() call logs ContextUsage.to_dict() at INFO |
1 |
REQ-107-021 |
yes | scripts/context_usage_report.py exists and can aggregate per-layer token spend from logs or a session record |
1 |
REQ-107-022 |
yes | The report script can aggregate per-volk and per-subject token spend | none |
REQ-107-030 |
yes | ArtifactStore accepts an optional output_dir and persists each artifact to a JSONL file |
none |
REQ-107-031 |
yes | ArtifactStore replays the JSONL file into the in-memory index on construction |
none |
REQ-107-032 |
yes | render_artifact_digest ranks artifacts by recency/relevance and renders only top-N within budget |
1 |
REQ-107-033 |
yes | PipelineArtifact carries optional spec and reqs fields |
none |
REQ-107-034 |
yes | ArtifactStore.by_requirement(req_id) returns artifacts whose reqs include that id |
none |
REQ-107-035 |
yes | An observed test proves artifacts survive an ArtifactStore reconstruct cycle |
none |
REQ-107-050 |
yes | get_code tool returns content or signature for a canonical code entity id |
4 |
REQ-107-051 |
yes | get_tests tool returns covering tests for a canonical code entity |
4 |
REQ-107-052 |
yes | get_requirements tool returns traced requirements for a canonical code entity |
5 |
REQ-107-053 |
yes | search_code tool returns semantic-search context for a natural-language query |
2 |
REQ-107-054 |
yes | Tools degrade gracefully when Neo4j or Memgraph is unavailable | 11 |
REQ-107-055 |
yes | Tools are registered as SPEC-062 native tools and exposed to Architect, CodeDirector, QA, and Documentor | 1 |
REQ-107-056 |
yes | Observed tests prove each tool against real Neo4j/Memgraph | none |
REQ-107-070 |
yes | UnifiedContextAssembler caches rendered ContextPack output by (snapshot_id, volk_alias, subject) for the lifetime of a pipeline run |
none |
REQ-107-071 |
yes | An observed test proves the cache returns identical context parts for repeated calls with the same key | none |
REQ-107-080 |
yes | GraphContext populates a recent_changes section from explicit Change nodes, NEXT_VERSION chains, or file timestamp properties |
none |
REQ-107-081 |
yes | GraphContext populates a similar_patterns section with files sharing base classes or protocols with target files |
none |
REQ-107-082 |
yes | GraphContext populates a semantic_files section via the Memgraph semantic-search bridge |
none |
REQ-107-083 |
yes | GraphContext populates requirements and decisions sections by traversing TRACES_TO and APPLIES_TO relationships |
none |
REQ-107-084 |
yes | Requirement closure walks REQUIRES edges up to a bounded depth and returns shortest-hop records |
none |
REQ-107-090 |
yes | Runtime signals (Message, ToolCall, Decision, GatewayEvent) are persisted to Neo4j with deterministic ids |
none |
REQ-107-091 |
yes | Runtime signals link to mentioned File/Class/Function nodes via MENTIONS edges |
none |
REQ-107-092 |
yes | Kernel pipeline dispatch, orchestrator runner, and BaseAgent wire the RuntimeSignalWriter |
none |
REQ-107-100 |
yes | GraphContextValidator validates LLM responses against core entities cited in graph_context |
none |
REQ-107-101 |
yes | ValidatorLoop retries LLM calls with expanded context when validation fails |
none |
REQ-107-102 |
yes | ContextExpansion controls extra_hops, include_tested_by, include_requirement_closure, and limit_multiplier |
none |
REQ-107-110 |
yes | ast-sync passes --incremental, warm-starting from the snapshot on disk and re-parsing only changed files (60.7s → 8.3s on a no-change run; 2819 modules before and after) |
none |
REQ-107-111 |
yes | ast-sync --snapshot-only runs steps 1/1b and stops, skipping the frozen-monorepo serialization and deep comparison |
none |
REQ-107-112 |
yes | main() parses the argv it is given; argv=None inheriting sys.argv aborts an in-process caller under pytest with SystemExit(2) |
none |
REQ-107-113 |
yes | Freshness is evaluated at the point of USE and scoped to the files the task touches, not across the whole tree | none |
REQ-107-114 |
yes | A consumer that cannot reach the KB distinguishes "queried, answer is nothing" from "could not query" | none |
REQ-107-115 |
yes | Blast radius resolves impact targets from the module's parsed AST, not module-api (__all__ is declared by only 136 of 2816 modules — 4.8%) |
none |
REQ-107-116 |
yes | Every bound on an answer (fan-out, parse caps, project scoping) is stated in the output | none |
REQ-107-117 |
yes | code_intel.py impact accepts many qnames in one invocation, and the six-symbol fan-out cap is removed rather than documented (it left 456 of 2485 symbols — 18% — unqueried) |
none |
REQ-107-120 |
yes | claude/ in this repo is the source of truth; ~/.claude is a deployment of it and nothing is authored in the deployed tree |
none |
REQ-107-121 |
yes | The deployment copies files — no symlink from ~/.claude into a checkout |
none |
REQ-107-122 |
yes | The deployment owns a uv venv on python3.14t, and ~/.claude/.envrc auto-activates it under direnv |
none |
REQ-107-123 |
yes | The installer rewrites settings.json hook commands to the venv's absolute interpreter, because Claude Code spawns hooks from the project's cwd where direnv never applies |
none |
REQ-107-124 |
yes | The installer records the source commit and refuses success unless every deployed file is byte-identical to that commit's blob | none |
REQ-107-125 |
yes | The drift comparison survives SIGPIPE and trailing-newline stripping — both previously made it report nothing, which reads as a pass | none |
REQ-107-126 |
yes | Deployed files are enumerated, never selected by filename heuristic (--exclude='test_*.py' once deleted 8 live files including a registered hook) |
none |
REQ-107-127 |
yes | A verification that matched zero files fails (CHECKED -gt 0 asserted explicitly) |
none |
REQ-107-128 |
yes | The deployed venv is verified free-threaded, and no deployed file references the source checkout | none |
REQ-107-130 |
yes | ship runs verify → push → deploy → verify-the-deployment in one sequence |
none |
REQ-107-131 |
yes | ship is reachable by name on PATH, and the installer verifies that by name rather than by path |
none |
REQ-107-132 |
yes | Each project states its own gate in ship; an unknown project fails rather than shipping unverified |
none |
REQ-107-133 |
yes | The dirty-tree check excludes tooling-rewritten paths, narrowly — sibling hand-written pages in the same directory still block | none |
REQ-107-134 |
yes | A Stop hook refuses to end a turn while a watched repo holds local-only commits | none |
REQ-107-135 |
yes | That hook stays quiet while a ship for the repo is already running, detecting it by whole-argv-element equality against the resolved executable (pgrep -f "bin/ship" substring-matched its own poller and returned true unconditionally) |
none |
REQ-107-136 |
yes | In-flight detection is by process, not lockfile — a lockfile outliving a SIGKILL would suppress the guard permanently | none |
REQ-107-137 |
yes | --no-verify states that it skips only this script's gate, and warns that an installed pre-push hook still runs |
none |
Code that traces this spec
Checklists
checklists/requirements.md
The requirement list above comes from this file; 73 of 73 checkbox items are ticked.
Full spec text
Verbatim from specs/107-context-injection-improvements/spec.md; headings are pushed down two levels.
SPEC-107: Context-Injection Improvements
Version: 0.4.0 (draft)
Date: 2026-07-14
Status: Draft — Phase 2 context layers landed (semantic, AST, recent changes, similar patterns, requirement closure, runtime signals, validator loop); FR-012 snapshot/KB freshness added 2026-08-13; FR-013 (the machinery is a deployed artifact) and FR-014 (shipping is mechanical) added 2026-08-14
Depends on: SPEC-008 (knowledge graph), SPEC-009 (volk context injection), SPEC-106 (context semantic layer)
Problem
The existing context assembler (src/orchestrata_ultimate/context/assembler.py) has the right layered shape, but several layers are either empty in production or under-used:
- Session/goal context is implemented (
mission_context.render_mission_context) but never fed because production callers passcontext_data=None. - The
planlayer is budgeted but never populated. - Memory recall loads an unbounded number of entries and scores them in Python.
- The semantic graph (
src/orchestrata_ultimate/semantic_graph/) and the serialized AST (plans/ast-ultimate.json) are rich data sources that are not automatically injected. - Context usage per layer is not logged, so optimization is blind.
- The artifact store is in-memory only and does not paginate digests.
Ruling constraints
- No LLM-driven selection. All context ranking and filtering remains deterministic, matching SPEC-106 FR-003.
- Backward-compatible assembler contract.
UnifiedContextAssembler.assemble()keeps the same signature; new layers are optional parts. - Degrade, don't crash. Every new source must have a no-op fallback when its backend (Neo4j, Memgraph, Redis, Prolog) is unavailable.
- Observed tests only. No mocks for production context paths; tests use real MemoryEngine/Neo4j/Memgraph where applicable.
FR-001: Session and plan context
PipelineRunnerand gateway paths must loadContextDatafrom the activeSessionControllerand pass it to the assembler.mission_context.render_mission_contextbecomes themissionlayer.- A new
plan_contextprovider renders recent pipeline outcomes and next planned stages into the existingplanbudget. - A
PlanParserreads the active plan markdown (e.g.plans/execution-plan.md) into structured steps. Goalpersists acurrent_stepindex; the session controller exposesadvance_goal_stepandset_goal_step.plan_contextrenders the current step in full, the next two step titles, and summaries of the last three completed steps.
FR-002: Bounded memory recall
MemoryEngine.search/RedisMemoryStore.retrieve_by_scopemust accept and enforce amax_entriesand/ormax_age_secondspre-filter before similarity scoring.- Defaults are chosen to keep recall latency under 100ms and memory layer under its token budget.
FR-003: Semantic-graph context layer
- A
SemanticGraphContextProviderqueries the Memgraph semantic graph for chunks relevant to the user message. - It contributes a
semanticpart toContextAssemblywhen the subject isimplement,fix_issues, orreview_and_test. - Falls back to empty if Memgraph is unavailable.
FR-004: AST-derived context layer
- AST complexity, branch, and typed call-graph signals from
plans/ast-ultimate.jsonmust be queryable from Neo4j or directly from the AST snapshot. - An
ast_contextpart (<=500 tokens) highlights the most complex or most-called symbols related to the task entities.
FR-005: Context telemetry
- Every
assemble()call logsContextUsage.to_dict()at INFO. - A new
scripts/context_usage_report.pyaggregates per-layer spend over a session.
FR-006: Artifact store refactor
ArtifactStorepersists each artifact to a timestamped JSONL file under a configurableoutput_dir.- On construction it replays the JSONL into the in-memory index.
render_artifact_digestranks artifacts by recency and relevance and renders only the top-N within budget.- Artifacts carry
spec/reqsfields and can be retrieved by requirement id.
FR-007: On-demand context tools
get_code(entity_id)returns the stored content or signature for a canonical code entity (File/Class/Function/Method) from the knowledge graph.get_tests(entity_id)returns the tests that cover a canonical code entity.get_requirements(entity_id)returns theRequirementItem/Requirementchain traced by a canonical code entity.search_code(query, target_files)returns a semantic-search context block by querying the Memgraph semantic graph and mapping chunk file paths back to Neo4jFileroot entities.- All four tools degrade gracefully when Neo4j or Memgraph is unavailable, returning a clear "unavailable" result rather than raising.
- Tools are registered as SPEC-062 native tools and exposed to the volks that need them (Architect, CodeDirector, QA, Documentor).
FR-008: Context pack caching
UnifiedContextAssemblercachesContextPack.render()output by(snapshot_id, volk_alias, subject)for the lifetime of a pipeline run, avoiding redundant KG queries.- The cache key includes the graph snapshot/version so that a new sync invalidates prior packs.
FR-009: Expanded graph-context sections
GraphContextpopulatesrecent_changesfrom explicitChangenodes,NEXT_VERSIONchains, or file timestamp properties.GraphContextpopulatessimilar_patternswith files whose classes share base classes or protocols with target files.GraphContextpopulatessemantic_filesvia the Memgraph semantic-search bridge.GraphContextpopulatesrequirementsanddecisionsby traversingTRACES_TOandAPPLIES_TOrelationships, includingREQUIRESclosure up to a bounded depth.
FR-010: Tier-H runtime signal ingestion
- Runtime signals (
Message,ToolCall,Decision,GatewayEvent) are persisted to Neo4j with deterministic ids. - Signals link to mentioned
File/Class/Functionnodes viaMENTIONSedges. - Kernel pipeline dispatch, orchestrator runner, and
BaseAgentwire the sharedRuntimeSignalWriter.
FR-011: Validator feedback loop
GraphContextValidatorvalidates LLM responses against core entities cited ingraph_context.ValidatorLoopretries LLM calls with expanded context when validation reports missing citations.ContextExpansioncontrolsextra_hops,include_tested_by,include_requirement_closure, andlimit_multiplier.
FR-012: Snapshot and KB freshness
The AST snapshot (plans/ast-ultimate.json) is the input every deterministic
context layer is derived from: the Prolog code-intelligence KB is rebuilt from it,
FR-004's AST layer reads it, and the gate fact base is extracted from it. A stale
snapshot therefore does not weaken those layers, it makes them confidently wrong —
the consumer has no way to tell an answer about deleted code from a current one.
ast-syncMUST pass--incrementalto the serializer, warm-starting from the snapshot already on disk and re-parsing only files whose content changed. The serializer guarantees byte-identical output either way, so this is a cost change, not a result change. (Measured 2026-08-13, no-change run: 60.7s → 8.3s; 2819 modules before and after, none gained or lost.)ast-sync --snapshot-onlyMUST run steps 1/1b and stop, skipping the frozen monorepo serialization and the deep comparison. This is the only stage a freshness refresh needs. (Measured: worth ~6s of the above, i.e. the smaller saving — step 1 was the cost.)main()MUST parse the argv it is given.argv=Nonereadssys.argv, so an in-process caller inherits the hosting process's command line; under pytest that aborts the pipeline withSystemExit(2).- Freshness MUST be evaluated at the point of USE and scoped to the files a task touches. A whole-tree check costs more than it is worth, and an unrelated module's edit cannot invalidate an injection about this one.
- A consumer that cannot reach the KB MUST distinguish "queried, and the answer is nothing" from "could not query". Collapsing them sends the reader hunting for a broken tool when the tool answered correctly.
- Blast radius MUST resolve its impact targets from the module's parsed AST, not
from
module-api.module-apireports__all__, which only 136 of 2816 modules (4.8%) declare, so KB-sourced resolution returned an empty symbol list — reported as "module not in the KB" — for ~95% of the tree. - Any bound on the answer (impact fan-out, parse caps, project scoping) MUST be stated in the output. An unstated bound reads as a complete answer.
- A bound that exists only to pay for a per-call cost MUST be removed once that
cost is amortised, not merely disclosed. Blast radius capped fan-out at six
symbols per module because each impact query was a separate 7.4s process
start; measured across orchestrata + knowledge-wiki, that cap left 456 of 2485
symbols (18%) unqueried, concentrated in the 87 of 757 modules (11%) with more
than six public symbols — the large modules whose blast radius matters most.
code_intel.py impactnow accepts many qnames in one invocation, so the cost is the process start (7.4s) and not the query (0.02s): a whole module now costs what one symbol used to (3 symbols in 7.15s, against 22.2s before). The cap is gone rather than documented.
FR-013: The injection machinery is a deployed artifact, not a home directory
Every layer above is delivered by hooks, tools and skills that Claude Code loads
from ~/.claude. Until 2026-08-14 that directory was the only copy: unversioned,
un-reviewed, and un-restorable, which made the injection layer the least
governed code in the project while being the code that runs on every prompt.
- The source of truth MUST be
claude/in this repo, and~/.claudeMUST be a deployment of it. The dev folder is where hooks, tools, agents, skills and commands are edited; nothing is authored in the deployed tree. - The deployment MUST copy. No symlink from
~/.claudeinto a checkout — that is deploy-in-place wearing a disguise: the running config silently follows an uncommitted edit, a rebase, or a branch switch. - The deployment MUST own a venv, created with
uvonpython3.14t, and~/.claude/.envrcMUST auto-activate it under direnv. The hooks previously ran under/usr/bin/python3with the GIL enabled — platform convention 1 violated in the one place that executes on every single prompt. - direnv is NOT what makes the hooks correct, and the spec MUST NOT be read as
saying so. Claude Code spawns hooks from the project's cwd, so nothing direnv
does reaches them; the installer additionally rewrites
settings.jsonhook commands to the venv's absolute interpreter. Both are required; neither substitutes for the other. - The installer MUST record the source commit and then REFUSE success unless
every deployed file is byte-identical to that commit's blob. Two ways this
check silently lied and were fixed: piping
git cat-fileintoheadunderpipefailkilled the comparison with SIGPIPE (exit 141) and reported nothing, which reads as a pass; and$(...)stripping trailing newlines flagged two byte-identical templates as drift. - The installer MUST NOT infer what to deploy from filename heuristics. An
--exclude='test_*.py'rule intended to skip test fixtures deleted eight live files, including the registered hooktest_isolation_hook.py. Excludes are enumerated, never pattern-guessed. - A verification that matches zero files MUST fail.
CHECKED -gt 0is asserted explicitly, because a drift check whose glob matches nothing otherwise passes loudest of all.
FR-014: Shipping is mechanical
Context injection that exists only as a local commit changes nothing: worktrees
branch off origin/master, so an unpushed commit leaves the next delegated task
building on a base that does not contain it. This is the drift that left the
claude/ dev folder 8831 lines behind the live config.
shipMUST run verify → push → deploy → verify-the-deployment in one sequence, and MUST be reachable by name onPATH. An installer that reports OK for a commandcommand -vcannot find has verified the wrong thing.- Each project MUST state its own gate in
ship. A gate inferred from a file listing is how a green "verification" gets reported for a suite that never ran; an unknown project fails rather than shipping unverified. - The dirty-tree check MUST exclude paths the tooling itself rewrites, and that
exclusion MUST be narrow. a live-data refresher refreshes four wiki live-data pages every
30 minutes with only a
date:and_Refreshed_change, which made ship fail on a 30-minute cycle; the sibling hand-written pages in the same directory still block, because their changes are real. - A Stop hook MUST refuse to end a turn while a watched repo holds commits that
exist only locally, and MUST stay quiet while a
shipfor that repo is already running. It detects the in-flight ship by whole-argv-element equality against the resolved executable: the first attempt usedpgrep -f "bin/ship", which substring-matches the entire command line and therefore matched the wait-loop polling for ship and the debug command inspecting it — returning true unconditionally and silently disabling the guard permanently. - Detection MUST be by process, not lockfile. A lockfile outlives a SIGKILL and would then suppress the guard forever, failing in the direction that hides unshipped work — the exact failure the hook exists to prevent.
--no-verifyMUST state what it actually skips. It skips this script's gate and is never passed togit, so a repo with a pre-push hook still runs it; announcing "gate skipped" and then sitting in a ten-minute suite reads as a hang.