Orchestrata Ultimate › Plans › context-injection-evolution-plan

Context Injection Evolution Plan

Source: plans/context-injection-evolution-plan.md at 8a2cb42ad · last changed 2026-07-27 · first committed 2026-07-13 · 367 lines.

Prose pending.

Plan text

Context Injection Evolution Plan

Analysis of how the project currently assembles an agent's prompt context, plus a concrete plan to make far better use of the AST, Neo4j knowledge graph, and session/plan/memory context — while keeping prompts inside a bounded, sliding context window.

1. Current state and gaps

1.1 What a volk receives today

The pipeline uses a layered prepend model: each context source is rendered as markdown and glued in front of the user message (src/orchestrata_ultimate/llm/client.py).

Layer Wired? Source
System prompt Yes Static per-volk persona string.
Raw intent Partial Only U:Cx (Concierge) sees the original human text.
Prior-stage artifacts Yes Manual payload key-copying in each volk's process() plus automatic digest via context_providers/artifact_digest.py in the assembler.
KG code context Yes knowledge_graph/context_injector.py::ContextInjector for subjects in INJECT_SUBJECTS.
Task-type context pack Yes context_pack.frame/select/pack/render via inject_for_task, consumed through the assembler.
Memory recall Yes memory/memory_context.py; both runners bind a MemoryEngine per thread_id and the assembler prepends summary + recall.
Session / goal / project state Partial context_providers/mission_context.py renders a compact block from ContextData, but the runner does not yet load the active session's ContextData into the assembler.
ArtifactStore feedback Yes context_providers/artifact_digest.py reads ArtifactStore.by_thread(thread_id) in the assembler.

The live injector already does several things well:

1.2 Key gaps

  1. No cross-layer budget. GraphContext, ContextPack, memory recall, and the user message each have their own limits, but no component tracks the total prompt size.
  2. Session/plan state is missing. Volks have no compact view of the active goal, pending work items, handoff notes, or the current plan step.
  3. Artifact feedback is manual. Prior-stage outputs rely on each volk copying the right payload keys.
  4. Memory is inconsistently wired. Recall only works when a MemoryEngine is registered for the thread.
  5. AST is underused. The AST engine (scripts/serialize_codebase_ast.py) produces branches, complexity, call graphs, untyped/dead-param reports, and skeletons, but none of it reaches volk prompts.
  6. Graph traversal is shallow. The live injector uses basename matching and a small fixed Cypher expansion; it does not exploit call-graph directionality, centrality, or semantic search.
  7. No feedback loop. GraphContextValidator checks whether the response references injected entities, but a failed validation does not reformulate the injected context.
  8. No on-demand retrieval. Everything is prepended. Volks cannot ask for more context when they need it.

2. Leveraging the AST

2.1 What the AST engine already provides

scripts/serialize_codebase_ast.py (and its FastAPI wrapper scripts/ast_api.py) reads live Python source and produces structured, prompt-usable output:

Subcommand Prompt value
symbols Exact API surface: classes, functions, signatures, files, lines.
branches Decision points (if, except, ternary) per function — exposes edge cases the agent must handle.
complexity Cyclomatic complexity ranking — surfaces refactor hotspots.
call-graph / refs Caller/callee neighborhoods and symbol usages.
gen-tests Branch-level pytest scaffolds the agent must make pass.
gen-skeleton Typed, @traces'd Python skeletons + leafmap.json / trace-map.json from a Mermaid contract.
trace-lint Dangling / placeholder / malformed @traces findings.
untyped, dead-params, unused, find-cycles Code-health backlog items.

The AST is also synced into Neo4j by src/orchestrata_ultimate/knowledge_graph/sync.py as 12 node labels (Project, Package, Module, Class, Function, Method, Parameter, Requirement, ImportNode, DecoratorNode, Spec, RequirementItem) and 18 relationship types (DEFINES, HAS_METHOD, IMPORTS, CALLS, INHERITS_FROM, TRACES_TO, TESTED_BY, etc.).

2.2 Proposed AST-driven context features

  1. Branch-aware prompts. Before modifying a function, run serialize codebase_ast branches --name <symbol> and inject the decision points. The agent must explicitly address each if/except path.
  2. Complexity-first dispatch. For fix_issues or review_and_test, inject the top-N complexity hotspots so the agent prioritizes high-risk functions.
  3. Call-graph neighborhood. For an implement or fix_issues packet, fetch call-graph --name <symbol> callers and callees and add them to the KG context. This reduces duplicate-code risk better than file-level imports.
  4. Skeleton contract workflow. Use gen-skeleton <contract.mmd> to emit the exact target shape, hand it to the agent as the implementation contract, then run skeleton-diff to detect structural escapes.
  5. Trace-lint preflight. Before a task claiming to satisfy requirements, run trace-lint and inject any dangling/placeholder findings. The agent must resolve them before marking work done.
  6. Untested-branch scaffolding. Combine branches + gen-tests to generate branch-level tests and require the agent to make them pass.
  7. API-on-demand. Keep scripts/ast_api.py running so volks can call /symbols, /branches, /refs, /complexity dynamically instead of parsing source themselves.

3. Leveraging the Knowledge Graph

3.1 What the KG already provides

3.2 Proposed KG-driven improvements

  1. Canonical-id lookups. Replace f.path CONTAINS $basename matching in ContextInjector._query_context with exact File.id lookups ({project}:{path}), then expand via DEFINES.
  2. Call-graph-biased expansion. Weight CALLS edges higher than IMPORTS; use in-degree centrality to surface hub functions.
  3. Requirement closure. Expand TRACES_TO to RequirementItem, then to parent Requirement/Spec, so the full "why" hierarchy is visible.
  4. Populate missing sections. GraphContext.recent_changes and similar_patterns are currently empty. Query updated_at/synced_at for recency and shared INHERITS_FROM bases for patterns.
  5. Semantic search bridge. Map top Memgraph vector-search chunks back to Neo4j File/Class/Function nodes by (file_path, start_line, end_line) and feed them as extra root entities into analyze_request.
  6. Validation feedback loop. Make GraphContextValidator node-aware (parse pack provenance or pass node ids). On failure, expand k_hop or include more TRACES_TO/TESTED_BY edges and re-pack.
  7. Runtime signal ingestion. Write Message, GatewayEvent, Decision, ToolCall nodes (Tier H labels) into Neo4j so Prolog relevance rules can prioritize recently changed or failing nodes for DEBUG tasks.
  8. Unify requirement identity. Merge or relate Requirement (code traces) and RequirementItem (spec traces) so requirement-sharing logic works across both.
  9. Cache invalidation. Tie query_engine.py LRU cache to graph synced_at or transaction ID so syncs clear stale entries.

4. Leveraging Session, Plan, and Memory Context

4.1 What session context already provides

src/orchestrata_ultimate/session/models.py defines:

ContextFileManager (session/context.py) already renders context.md via Jinja2 templates in session/templates/. The memory engine (memory/) provides hot (Redis) and cold (Neo4j) stores with a deterministic hash embedder and ContextCompressor. ArtifactStore captures per-stage outputs.

4.2 Proposed session-driven context features

  1. Mission block injection. Load ContextData once per pipeline run and render a compact block containing:
    • ProjectState status, branch, test state.
    • Active Goal statement and unmet criteria.
    • Top pending WorkItems.
    • Latest HandoffData (decisions, blockers, key files, alerts).
  2. Memory summary + recall. At pipeline start, call MemoryEngine.summarize(context_id=thread_id, max_tokens=512) for a run-level summary; keep per-task recall() for specifics. Prepend both.
  3. Artifact feedback loop. After each stage, ArtifactStore.add() already captures outputs. Query artifacts.by_thread(thread_id) and inject a chronological digest (stage, type, file path, 200-char preview).
  4. Plan window injection. Parse plans/execution-plan.md (or the active plan document) into steps. Maintain a current_step pointer in ProjectState or Goal. Inject the current step in full plus the next 2 steps as titles.
  5. Canonical ArtifactStore. Consolidate kernel/pipeline_artifacts.py::ArtifactStore and orchestrator/artifact_store.py so both runners feed the same artifact context.

5. Target architecture: Unified Context Assembler

Introduce one service that composes all context sources under a single token budget.

┌─────────────────────────────────────────────────────────────┐
│                   UnifiedContextAssembler                   │
│  inputs: payload, subject, task_type, thread_id, volk_alias  │
│  output: context_parts dict + token accounting               │
└─────────────────────────────────────────────────────────────┘
        │
        ▼
┌──────────────┐  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐
│ Session/Goal │  │ Memory       │  │ Artifact     │  │ KG / AST     │
│ Context      │  │ Summary      │  │ Digest       │ │ Context      │
└──────────────┘  └──────────────┘  └──────────────┘  └──────────────┘
        │                │                │                │
        └────────────────┴────────────────┴────────────────┘
                                │
                                ▼
                    ┌───────────────────────┐
                    │   ContextBudget       │
                    │   (enforced layering) │
                    └───────────────────────┘
                                │
                                ▼
                    ┌───────────────────────┐
                    │ effective_message     │
                    │ = join(parts) + user  │
                    └───────────────────────┘

Responsibilities:

Integration points:

5.1 The voluntary-use problem

Historical experiments show a consistent failure mode: even when instructions tell the model to use provided context, the model does not voluntarily pull or reference the data. It falls back on training-data shortcuts, hallucinates, or ignores the injected material.

This means context injection cannot rely on politeness. The system must force usage:

  1. Prepend, don't side-channel. Put context inside the user message tokens so the model must process it (already done in llm/client.py).
  2. Make references mandatory. Require the output to list every entity it used: classes, functions, requirements, decisions, prior artifacts. A response with no citations is invalid.
  3. Validate citations. GraphContextValidator must check that cited entities were actually injected and that injected critical entities were cited.
  4. Use small, unavoidable context. Inject only the entities the task cannot succeed without. A smaller critical set is harder to ignore than a large optional encyclopedia.
  5. Structured output. When possible, make the volk fill a template (e.g., "Entities modified:" + "Requirements satisfied:" + "Decisions respected:") rather than write free prose.
  6. Mandatory tool calls for expansion. On-demand retrieval should not be optional. The task prompt can require the model to call get_code/get_tests before producing output, and the pipeline rejects output that skipped the tool call.
  7. Retry with escalation. First failure: re-prompt with a warning. Second failure: shrink context to the single most relevant entity and ask again. Third failure: escalate to human.
  8. Reward signal via provenance. ContextPack already carries provenance comments. Extend this to require the model to echo the node_ids it acted on; score responses by citation coverage.

6. Context-window management strategy

6.1 Budget model

Reserve slots explicitly before the model sees the prompt. Example budget for a 128k-context model with ~8k output:

Slot Tokens Policy
System prompt 800 Fixed.
Mission / session state 400 Fixed; summarize if needed.
Plan window 600 Current step full; done/next summaries.
Memory summary + recall 700 Summary 500 + recall 200.
Artifact digest 500 Most recent first.
Graph context 2000 Evict peripheral → related → core.
Task context pack 1000 Task-type-specific.
User message remaining Guard headroom.
Output reserve 800 Never steal from this.

The assembler rejects or degrades gracefully when the total exceeds the model window.

6.2 Sliding plan window

For long-running plans:

6.3 Eviction policy

Use the existing ranking signals:

  1. Drop TIER_PERIPHERAL nodes.
  2. Drop TIER_RELATED nodes.
  3. Within a tier, drop highest hop_distance first.
  4. Never evict: active goal, current-step plan, blockers, failing tests, prior decisions, current diff.

6.4 On-demand retrieval

Instead of prepending everything, expose tools:

This keeps the default prompt small and lets volks expand context only when needed.


7. Phased implementation plan

Phase 0 — Foundations (suggested first milestone)

Goal: establish budget accounting, a single assembler, and close the most urgent gaps.

# Task Files to touch Acceptance criteria
0.1 Add ContextBudget dataclass src/orchestrata_ultimate/context/budget.py Model with slots for system, mission, plan, memory, artifacts, graph, task, user, output; total-window validation; per-layer reservation.
0.2 Add UnifiedContextAssembler skeleton src/orchestrata_ultimate/context/assembler.py Class accepts payload, subject, task_type, thread_id, volk_alias; returns {system, mission, memory, artifacts, graph, task, user} context parts and token counts; has a no-op implementation for graph/task that delegates to existing injector.
0.3 Artifact feedback via store-agnostic digest src/orchestrata_ultimate/context/providers/artifact_digest.py (new), src/orchestrata_ultimate/context/assembler.py Render a chronological digest from either ArtifactStore implementation without a broad refactor. Closes Gap E immediately.
0.4 Inject session / mission context src/orchestrata_ultimate/context/providers/mission_context.py (new), src/orchestrata_ultimate/context/assembler.py Render a ≤400-token block from ContextData with active goal, top 5 pending work items, and latest handoff decisions/blockers.
0.5 Wire assembler into both runners src/orchestrata_ultimate/kernel/pipeline_kg.py, src/orchestrata_ultimate/orchestrator/pipeline_runner.py Both runners call UnifiedContextAssembler.assemble() and pass the resulting context parts to call_llm (or keep the existing graph_context kwarg but populated by the assembler).
0.6 Add context-usage telemetry src/orchestrata_ultimate/context/assembler.py, logging Every LLM call logs context_usage JSON with tokens per layer.
0.7 Mandate citation output format src/orchestrata_ultimate/core/graph_directives.py, per-volk system prompts Every volk prompt includes an instruction to list every entity (class/function/requirement/decision/artifact) it used. System prompt templates updated.
0.8 Make validator citation-aware src/orchestrata_ultimate/knowledge_graph/graph_context_validator.py Validator parses citations from the response and checks that (a) cited entities were injected, (b) injected TIER_CORE entities were cited. Returns missing citations.

Phase 1 — AST-driven context

Goal: feed AST-derived facts into the assembler.

# Task Files to touch Acceptance criteria
1.1 AST API client src/orchestrata_ultimate/tools/ast_client.py (new) Typed client for scripts/ast_api.py endpoints (/symbols, /branches, /complexity, /call-graph, /refs, /gen-tests, /trace-lint). Caches responses per file mtime.
1.2 Branch context provider src/orchestrata_ultimate/context/providers/ast_branches.py (new) Given target symbols, returns a markdown block of if/except/ternary decision points.
1.3 Complexity hotspot provider src/orchestrata_ultimate/context/providers/ast_complexity.py (new) Returns top-N complexity functions in target files.
1.4 Call-graph neighborhood provider src/orchestrata_ultimate/context/providers/ast_call_graph.py (new) Returns callers/callees for target symbols.
1.5 Trace-lint preflight provider src/orchestrata_ultimate/context/providers/ast_trace_lint.py (new) Runs trace-lint on target files and returns findings; assembler includes them for implement/review_and_test subjects.
1.6 Skeleton contract integration src/orchestrata_ultimate/agents/volks/codedirector/scaffold/generator.py, src/orchestrata_ultimate/context/assembler.py When a payload carries a Mermaid contract, run gen-skeleton and inject the skeleton + leafmap as task_context.
1.7 Tests for providers tests/context/providers/test_*.py Each provider has unit tests with real AST fixtures; no mocks.

Phase 2 — KG enhancements

Goal: richer, canonical, feedback-aware graph context.

# Task Files to touch Acceptance criteria
2.1 Canonical file lookups src/orchestrata_ultimate/knowledge_graph/context_injector.py _query_context resolves target files to File.id first; no more path CONTAINS basename heuristic.
2.2 Populate recent changes src/orchestrata_ultimate/knowledge_graph/context_injector.py GraphContext.recent_changes queries File/Class/Function.updated_at and returns the most recently synced N items.
2.3 Populate similar patterns src/orchestrata_ultimate/knowledge_graph/context_injector.py GraphContext.similar_patterns returns files/classes sharing a base class or implementing the same protocol.
2.4 Requirement closure src/orchestrata_ultimate/knowledge_graph/context_injector.py _query_semantic_context walks TRACES_TO → RequirementItem → Requirement → Spec and renders the hierarchy.
2.5 Semantic search bridge src/orchestrata_ultimate/knowledge_graph/context_pack/selector.py, src/orchestrata_ultimate/knowledge_graph/semantic_bridge.py (new) Memgraph vector-search top-k chunks are mapped to Neo4j nodes by (path, line_range) and added as extra root entities in analyze_request.
2.6 Validator feedback loop src/orchestrata_ultimate/knowledge_graph/graph_context_validator.py, src/orchestrata_ultimate/context/assembler.py Validator receives injected node ids; on failure, assembler re-runs select with larger k_hop and/or expands TESTED_BY/TRACES_TO edges, then re-packs.
2.7 Runtime Tier H ingestion src/orchestrata_ultimate/knowledge_graph/runtime_writer.py (new), prolog/kg_relevance.pl Writer creates Message/GatewayEvent/Decision/ToolCall nodes; Prolog rules use kg_recent/1 and kg_failed/1 to boost relevance for DEBUG.
2.8 Unify requirement nodes src/orchestrata_ultimate/session/kg_sync/_stage_specs_traces.py, src/orchestrata_ultimate/knowledge_graph/canonical_schema.py Requirement and RequirementItem are linked by Requirement-[:HAS_ITEM]->RequirementItem; both participate in requirement-sharing logic.

Phase 3 — Session, memory, and plan window

Goal: make long-running sessions coherent and plan-aware.

# Task Files to touch Acceptance criteria
3.1 Memory summarization src/orchestrata_ultimate/memory/memory_engine.py, src/orchestrata_ultimate/agents/volks/memory_context.py Add summarize(context_id, max_tokens) using ContextCompressor; cache result per thread; assembler prepends it.
3.2 Universal memory binding src/orchestrata_ultimate/kernel/pipeline.py, src/orchestrata_ultimate/orchestrator/pipeline_runner.py, src/orchestrata_ultimate/agents/kernel_handler.py Both runners register a MemoryEngine for the thread in a try/finally.
3.3 Plan parser src/orchestrata_ultimate/planning/plan_parser.py (new) Parse plans/execution-plan.md (or any active plan markdown) into (step_num, title, goal, actions, verify, expected) tuples.
3.4 Plan window provider src/orchestrata_ultimate/context/providers/plan_window.py (new) Given current_step, renders current step full + next 2 titles + last 3 done summaries.
3.5 Current-step tracking src/orchestrata_ultimate/session/models.py (ProjectState/Goal), src/orchestrata_ultimate/session/controller/_goals.py Goal gains current_step field; goal advance CLI updates it; assembler reads it.
3.6 Artifact digest provider src/orchestrata_ultimate/context/providers/artifact_digest.py (new) Queries canonical ArtifactStore.by_thread(thread_id) and returns chronological digest.
3.7 End-to-end long-session test tests/session/test_context_window.py Simulate 20 turns; verify token budget holds and older turns are summarized, not dropped silently.

Phase 4 — Optimization and observability

Goal: make context assembly fast, fresh, and measurable.

# Task Files to touch Acceptance criteria
4.1 Context pack caching src/orchestrata_ultimate/context/assembler.py Cache ContextPack.render() output by (snapshot_id, volk_alias, subject) for the lifetime of a pipeline run.
4.2 Incremental sync hooks src/orchestrata_ultimate/knowledge_graph/sync.py, scripts/sync_ast_to_neo4j.py Add incremental_sync(module_paths); wire a post-save hook or watcher in development mode.
4.3 Query cache invalidation src/orchestrata_ultimate/knowledge_graph/query_engine.py LRU cache key includes graph synced_at; stale entries evicted after sync.
4.4 On-demand context tools src/orchestrata_ultimate/agents/volks/tools/context_tools.py (new) Implement get_code, get_tests, get_requirements, search_code tools registered in the volk tool registry.
4.5 Context dashboard docs/context-telemetry.md (new), logging Document context_usage log format; add a small script scripts/context_usage_report.py to aggregate per-volk / per-subject token spend.
4.6 Budget-driven stress test tests/context/test_budget.py Generate oversized context; verify assembler degrades gracefully (drops peripheral first, never exceeds model window, logs degradation reason).

Phase 5 — Cleanup (after the initial wave)

Goal: pay down the technical debt left behind to keep the initial wave small.

# Task Files to touch Acceptance criteria
5.1 Canonicalize ArtifactStore implementations src/orchestrata_ultimate/kernel/pipeline_artifacts.py, src/orchestrata_ultimate/orchestrator/artifact_store.py, all callers Merge the two PipelineArtifact/ArtifactStore implementations into one canonical version; update imports and tests in both runners and verification gate.
5.2 Remove basename heuristic from ContextInjector src/orchestrata_ultimate/knowledge_graph/context_injector.py _query_context uses canonical File.id / File.path + project_id lookups; no more path CONTAINS basename.
5.3 Consolidate context provider package src/orchestrata_ultimate/context/ or src/orchestrata_ultimate/context/providers/ Decide whether budget/assembler/providers belong under KG or a top-level context package; move and update imports if needed.

8. Acceptance criteria for the whole effort

  1. Every volk call logs context_usage with tokens per layer.
  2. Both PipelineRunner implementations use the same UnifiedContextAssembler.
  3. Session state (goal, handoff, pending work) is injected for all INJECT_SUBJECTS.
  4. Memory recall works in both choreographed and orchestrated runners.
  5. Artifact feedback is automatic, not manual payload copying.
  6. AST-derived context (branches, complexity, call graph, trace-lint) is available to at least implement, fix_issues, and review_and_test subjects.
  7. Graph context uses canonical File.id lookups and includes recent changes / similar patterns.
  8. GraphContextValidator failure triggers a one-step context expansion retry.
  9. A plan window (current step + compact done/upcoming) is injected for long-running plan execution.
  10. Total prompt size never exceeds the configured model window; peripheral nodes are evicted first.
  11. Volk outputs cite injected entities; GraphContextValidator rejects outputs missing mandatory citations.

9. Risks and non-goals

Risks

Non-goals


10. Summary of immediate next actions

If you want to start tomorrow, do these in order:

  1. Create ContextBudget and UnifiedContextAssembler skeleton.
  2. Pick one ArtifactStore and migrate the other runner to it.
  3. Inject a mission block from ContextData for INJECT_SUBJECTS.
  4. Fix ContextInjector._query_context to use canonical File.id lookups.
  5. Add MemoryEngine.summarize and bind memory universally in both runners.
  6. Mandate citations in every volk prompt and make GraphContextValidator enforce them.

Those six items close gaps D, E, and C from docs/context-injection.md and address the observed voluntary-use failure mode.