# Design: Project Map Prompt Injection ## Status | Field | Value | |---|---| | Phase | **Design** | | Based on | [Spec](spec.md) | | Next | Tasks | ## Design summary This change adds a runtime guidance layer on top of the paired map/index artifact system. The implementation should not redesign map generation or retrieval. Instead, it should add a well-scoped injection pipeline that: - emits honest startup hints before init, - preloads the root pair after init, - expands under a hybrid budget, - avoids redundant reinjection by scanning real outgoing context, - varies guidance strength through explicit modes, - proves correctness with integration-heavy validation. ## Affected areas ### Source files likely to change - `pi-extension.ts` - shared config handling (`src/config.ts` or equivalent) - runtime helpers for map/index discovery and injection selection - tests covering extension lifecycle and per-turn behavior - docs/runtime guidance surfaces if needed ### New modules likely to appear - `src/prompt-injection.ts` or equivalent runtime helper - optional helper for canonical marker construction / detection - optional helper for token-budget estimation across paired artifacts ## Architecture changes ### 1. Injection policy helper Centralize prompt-injection logic in one helper rather than scattering it across hooks. Suggested responsibilities: - determine current mode (`off` / `advisory` / `strong` / `strict`), - detect whether artifacts exist, - build pre-init startup hint, - build post-init root-pair payload, - estimate expansion budget, - choose additional artifacts under the budget, - construct a canonical injected marker/block, - scan outgoing context or payload for that marker, - decide whether reinjection is required. This helper is the main guard against drift between startup hints, context hooks, and strict-mode checks. ### 2. Injection surfaces Use different extension surfaces for different responsibilities. #### Pre-init / startup hint Use `before_agent_start` for lightweight startup guidance before real artifacts exist. Required behavior: - inject only a hint, - tell the agent to run `project_map_init`, - keep the hint visible and inspectable. #### Post-init root-pair preload Use `before_agent_start` to guarantee the initial post-init root-pair preload for a prompt. Required behavior: - inject root `.pi-map.index.md`, - inject root `.pi-map.md`, - optionally append brief protocol text only if needed by the selected mode. #### Relevant-turn reinjection Use `context` for relevant-turn checks. Required behavior: - inspect `event.messages`, - decide whether the canonical block is already present, - only add the root pair / budgeted expansion if absent, - avoid rescanning on every trivial turn in `strong` mode. #### Payload fallback Use `before_provider_request` only as a fallback or debugging surface when message-layer detection is insufficient. ### 3. Canonical marker design Deduplication depends on stable canonical detection. The implementation should stamp injected content with a canonical marker block. Recommended v1 shape: - a deterministic wrapper such as `` / ``, - normalized artifact identity lines for root `.pi-map.index.md` and root `.pi-map.md`, - trust-boundary text within the same wrapped block when the active mode requires it. Marker rules: - stable across turns, - independent of provider formatting quirks, - easy to scan in both `event.messages` and final provider payload, - robust enough that root-pair presence can be detected without brittle full-text matching. ### 4. Budgeting Budgeting should be deterministic and layered. #### Context-window discovery and fallback - Prefer active-model context-window metadata exposed by Pi runtime/model selection. - If context-window metadata is unavailable, fall back to the configured absolute cap. - The fallback path should be explicit in logs/debug behavior so budget decisions remain auditable. #### Required order 1. compute effective budget from relative percentage + optional absolute cap, 2. reserve the root pair first, 3. expand outward using a deterministic traversal order, 4. stop when the budget would be exceeded. #### Traversal strategy The spec does not force exact traversal heuristics, but the design should prefer: - root pair first, - shallow structural coverage before deep leaves, - predictable order over opaque scoring. This keeps automatic injection understandable and auditable. ### 5. Mode control surface Expose a config/runtime setting for the four modes. Because the current project config is loaded from flat JSON in `.pi-project-map.json`, v1 should prefer a flat compatible shape rather than forcing an immediate nested/YAML migration. Suggested v1 config shape: ```json { "promptInjectionMode": "strong", "contextBudgetPercent": 15, "contextBudgetMaxTokens": 100000 } ``` Migration note: - keep existing flat JSON loading in `src/config.ts`, - treat these as new additive keys, - decide explicitly whether existing `contextBudget` is deprecated, ignored for injection, or retained only for older LLM-analysis paths. Optional runtime UX may later mirror thinking-level controls, but v1 implementation can start with config-driven mode selection as long as the semantics are the same. ### 6. Mode semantics #### `off` - no automatic artifact injection, - no startup/init hint beyond existing tool/docs discovery. #### `advisory` - startup/init hints enabled, - optional root-pair preload, - light reminders, - weaker reinjection behavior. #### `strong` - root-pair preload required, - budgeted expansion required, - relevant-turn reinjection checks required, - reminders before edits and architecture-sensitive reasoning. #### `strict` - everything in `strong`, plus: - when protocol path is missing during sensitive actions, require explicit bypass justification. ### 7. Relevant-turn detection `strong` mode should not rescan on every turn. Relevant-turn triggers should be mapped to concrete runtime signals where possible: - agent start, - edit intent / edit tool preparation, - architecture-sensitive planning prompts, - compaction completion, - root-pair artifact change detection. This will likely require some combination of: - hook-local heuristics, - observed tool calls, - file timestamp/hash checks for root artifacts, - compaction event handling. ### 8. Visibility model The design should preserve mixed visibility. - startup hints: visible and inspectable, - raw injected artifact blocks: agent-visible by default, - the fact that automatic injection exists should remain discoverable. The implementation may use hidden custom message types for artifact payloads, but should not make startup/init behavior opaque. ### 9. Validation strategy This change is validation-heavy. Unit tests alone are not enough, because the main risk is runtime interaction between hooks, message mutation, compaction, and reinjection. #### Integration focus areas - startup before init, - startup after init, - root-pair marker insertion, - reinjection suppression when marker already exists, - reinjection after compaction, - reinjection after artifact mutation, - mode differences, - strict-mode bypass path, - mixed visibility expectations, - synthetic event-sequence coverage for relevant-turn heuristics such as edit-intent, architecture-sensitive reasoning, compaction, and artifact invalidation. ### 10. Documentation impact Runtime guidance docs must align with the spec, but retrieval docs remain separate. Docs should teach: - startup hint before init, - root-pair automatic preload after init, - trust boundary, - mode ladder, - relevant-turn reinjection behavior, - integration-test importance. ## Risks | Risk | Mitigation | |---|---| | Message-level scanning misses provider serialization quirks | Add payload fallback via `before_provider_request` | | Root-pair marker becomes brittle | Use deterministic boundaries and normalized artifact identity lines | | 15% + 100k is too aggressive in some fleets | Keep both knobs configurable and document the opinionated default | | Relevant-turn detection becomes fuzzy | Centralize detection heuristics and prove them with integration tests | | `strict` mode causes friction | Keep `strong` as default and isolate strict-only bypass behavior | ## Settled defaults - Root pair is always guaranteed after init. - Reinjection avoidance is based on canonical outgoing-context scanning. - Retrieval remains out of scope. - Mixed visibility is the intended baseline. - Four modes exist, with `strong` as default. - Default budget is 15% of active context window, capped at 100k tokens.