spec(prompt): add project map prompt injection change set

This commit is contained in:
2026-06-11 22:56:20 +02:00
parent 58e8bd31d3
commit c11d49d015
4 changed files with 639 additions and 0 deletions
@@ -0,0 +1,235 @@
# 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 `<!-- PI_MAP_ROOT_PAIR_START -->` / `<!-- PI_MAP_ROOT_PAIR_END -->`,
- 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.
@@ -0,0 +1,88 @@
# Proposal: Project Map Prompt Injection
## Status
| Field | Value |
|---|---|
| Phase | **Proposal** |
| Based on | Grill Me checkpoint + runtime/doc inspection |
| Next | Spec |
## Problem
`pi-project-map` currently provides strong generated artifacts and tool surfaces, but weak automatic runtime guidance.
Today the repo has:
- a one-time `before_agent_start` hint,
- a `session_start` dirty-state UI notification,
- tool metadata and docs,
- generated `.pi-map.md` / `.pi-map.index.md` artifacts.
But it lacks a clear spec for:
1. when maps/indexes should be injected automatically,
2. how much should be injected under a context budget,
3. how reinjection should avoid wasting prompt space,
4. how visible that guidance should be to the user,
5. how strong enforcement should be,
6. how to validate this behavior with integration tests.
## Proposed change
Adopt a runtime prompt-injection policy for project-map that:
- always injects the **root pair** after init,
- expands outward under a **hybrid context budget cap**,
- avoids redundant reinjection by scanning the actual outgoing context,
- keeps **retrieval guidance separate** from this spec,
- exposes **four configurable guidance modes** (`off`, `advisory`, `strong`, `strict`),
- uses **mixed visibility**: user-visible startup hints, agent-visible artifact injection,
- requires **extensive integration tests** for reinjection and context-scanning behavior.
## In scope
- [ ] Define pre-init startup hint behavior
- [ ] Define post-init automatic root-pair preload behavior
- [ ] Define hybrid budget defaults and config shape
- [ ] Define outgoing-context scanning for reinjection avoidance
- [ ] Define visibility model for hints vs injected artifacts
- [ ] Define configurable guidance-strength modes and default
- [ ] Define relevant-turn reinjection triggers for `strong`
- [ ] Define strict-mode bypass-justification behavior at the spec level
- [ ] Define validation and integration-test expectations
- [ ] Define implementation slices for runtime hooks/config/tests/docs
## Out of scope
- [ ] Redesign retrieval ranking or `project_map_context`
- [ ] Merge retrieval behavior into this policy spec
- [ ] Redesign paired artifact contents
- [ ] Introduce vector stores, Engram, or external retrieval backends
- [ ] Guarantee provider-agnostic perfect token counting beyond best-effort budgeting
## Decisions from grilling
| Topic | Decision |
|---|---|
| Change slug | `project-map-prompt-injection` |
| Scope split | Retrieval remains a separate spec |
| Pre-init | Inject only a lightweight `project_map_init` hint |
| Fake/synthetic maps before init | No |
| Guaranteed minimum | Always inject root `.pi-map.index.md` + root `.pi-map.md` |
| Budget model | Hybrid cap |
| Budget config | Relative percentage + optional absolute cap; smaller wins |
| Budget default | 15% of context window, capped at 100k tokens |
| Reinjection avoidance | Canonical marker scanning in actual outgoing context |
| Visibility | Mixed |
| Mode set | `off`, `advisory`, `strong`, `strict` |
| Default mode | `strong` |
| `strong` triggers | Relevant turns only |
| Validation | Extensive integration tests required |
## Success criteria
- [ ] The spec clearly defines what is injected before and after init
- [ ] The spec clearly defines how much is injected and how budgets are applied
- [ ] The spec clearly defines when reinjection checks happen and how duplicates are avoided
- [ ] The spec clearly defines mode semantics and the default mode
- [ ] The spec clearly separates runtime injection policy from retrieval behavior
- [ ] The implementation plan can be executed in narrow, reviewable slices
- [ ] The validation section makes integration coverage a first-class requirement
@@ -0,0 +1,226 @@
# Spec: Project Map Prompt Injection
## Status
| Field | Value |
|---|---|
| Phase | **Spec** |
| Based on | [Proposal](proposal.md) |
| Next | Design |
## Overview
`pi-project-map` must define a deliberate automatic runtime guidance model for map/index usage. After init, the system should preload the root pair, expand under a bounded context budget, avoid redundant reinjection by inspecting actual outgoing context, and scale enforcement through configurable guidance modes.
This spec covers **automatic injection and maintenance guidance only**. Retrieval behavior remains covered by `map-context-retrieval`.
## Decisions
| # | Question | Answer |
|---|---|---|
| 1 | Retrieval included in this spec | No |
| 2 | Pre-init behavior | Lightweight init hint only |
| 3 | Synthetic map content before init | No |
| 4 | Guaranteed post-init minimum | Always inject root pair |
| 5 | Budget model | Hybrid cap |
| 6 | Budget knobs | Relative percentage + optional absolute cap |
| 7 | Budget default | 15% of context window, capped at 100k tokens |
| 8 | Reinjection avoidance | Canonical marker scan in outgoing context/payload |
| 9 | Visibility | Mixed |
| 10 | Mode set | `off`, `advisory`, `strong`, `strict` |
| 11 | Default mode | `strong` |
| 12 | `strong` reinjection cadence | Relevant turns only |
| 13 | Validation expectation | Extensive integration coverage |
## Functional requirements
### 1. Pre-init behavior
Before generated map/index artifacts exist, the extension must inject only a lightweight startup hint.
#### Required behavior
- The hint must tell the agent that the project-map extension is active.
- The hint must instruct the agent to run `project_map_init`.
- The hint must not claim that real map/index artifacts already exist.
- The system must not inject synthetic or fake map content before real artifacts are generated.
### 2. Post-init guaranteed preload
After real artifacts exist, the system must guarantee a minimum preload of the root pair:
- root `.pi-map.index.md`
- root `.pi-map.md`
This root pair is the minimum automatic preload before budgeted expansion begins.
### 3. Trust boundary
The runtime policy must encode the trust boundary:
> **index routes, map orients, source decides**
Required implications:
1. Indexes are navigation aids, not final authority.
2. Maps provide orientation and architectural context, not final authority.
3. Source must remain the final authority before edits or exact behavioral claims.
4. If injected artifacts and source disagree, source wins.
### 4. Budget model
Automatic expansion beyond the root pair must use a hybrid context budget cap.
#### Knobs
The system must support:
- a **relative context-budget percentage**,
- an **optional absolute token cap**.
If both are configured, the smaller effective budget wins.
#### Default budget
The default must be:
- **15%** of the active model context window,
- **100k tokens** absolute cap,
- use the smaller effective budget.
#### Context-window discovery and fallback
- The runtime should derive the active model context window from Pi model metadata when available.
- If the active model context window is unavailable, the runtime must still honor the absolute cap.
- In that fallback case, implementations may skip the relative calculation and use the absolute cap as the effective budget.
#### Expansion rules
- Root pair injection happens before budgeted expansion.
- Additional map/index artifacts are added only while the budget allows.
- The expansion strategy should prefer shallow, high-value structural coverage over deep indiscriminate expansion.
- The spec does not require exact provider-token parity; best-effort budgeting is acceptable if it is deterministic and auditable.
### 5. Reinjection avoidance
The extension must avoid redundant reinjection once the root pair is already in active outgoing context.
#### Required definition
"Already in context" must be defined by scanning the **actual outgoing context**, not only by session guesses.
#### Required behavior
- Before reinjecting, scan per-turn `event.messages` for a stable canonical marker or normalized injected root-pair block.
- If message-layer evidence is insufficient, the runtime may additionally inspect the final provider payload.
- Inject only when the canonical root-pair marker/block is absent.
- The spec must allow implementation via explicit scanning logic even if Pi does not expose a convenience API.
### 6. Visibility model
The system must use mixed visibility.
#### Required behavior
- Lightweight startup/init hints should be user-visible and inspectable.
- Automatic artifact injection may remain agent-visible by default.
- The spec should preserve debuggability: implementations should make the presence of automatic guidance discoverable, even when raw artifact blocks are not fully dumped to the user on every turn.
### 7. Guidance-strength modes
The system must support four named modes:
- `off`
- `advisory`
- `strong`
- `strict`
#### Protocol path definition
For this spec, the **protocol path** is present when the current outgoing context contains:
1. the canonical injected root-pair block, or an equivalent canonical marker proving that root `.pi-map.index.md` and root `.pi-map.md` are already present, and
2. the trust-boundary instruction establishing that `index routes, map orients, source decides`.
If either element is missing for a sensitive action, the protocol path is missing.
#### Mode semantics
- **`off`**
- no automatic injection beyond tool/docs discovery.
- **`advisory`**
- inject startup/init hints,
- allow optional root-pair preload,
- use light reminders.
- **`strong`**
- inject root pair,
- expand under the configured budget,
- run reinjection checks on relevant turns,
- remind before edits or architecture-sensitive reasoning.
- **`strict`**
- same as `strong`, plus:
- require explicit bypass justification before sensitive edits or architectural claims when the protocol path is missing.
#### Default mode
The default mode must be **`strong`**.
### 8. `strong`-mode trigger timing
In `strong` mode, reinjection checks must happen on relevant turns only.
Required triggers:
- agent start,
- before edits,
- before architecture-sensitive reasoning,
- after compaction,
- after root-pair artifact changes.
Required non-trigger:
- do not rescan on every trivial turn.
### 9. Implementation surfaces
The runtime may achieve this behavior through a combination of:
- startup hooks,
- per-turn context hooks,
- provider-payload hooks,
- prompt guidance,
- generated root artifacts.
The spec intentionally does **not** require one exact implementation mechanism, but it does require the observable runtime behavior above.
### 10. Validation requirements
The implementation must be proven with extensive integration coverage.
At minimum, integration coverage must validate:
- pre-init hint behavior,
- post-init root-pair injection,
- budgeted expansion behavior,
- canonical-marker dedupe,
- reinjection after compaction,
- reinjection after root-pair artifact changes,
- mixed visibility behavior,
- guidance-mode differences,
- strict-mode bypass behavior where implemented.
## Non-functional requirements
- Keep the policy explicit and auditable.
- Keep retrieval out of scope for this spec.
- Prefer deterministic behavior over opaque heuristics where possible.
- Optimize for modern large-context models, while retaining a hard ceiling.
- Preserve source as final authority.
## User flows
### Flow 1: New repo, maps not initialized
1. Agent starts in a repo without project-map artifacts.
2. Runtime injects a lightweight visible startup hint.
3. Agent is instructed to run `project_map_init`.
4. No fake artifact content is injected.
### Flow 2: Normal initialized repo in `strong` mode
1. Agent starts.
2. Runtime checks outgoing context for canonical root-pair marker.
3. If absent, inject root pair and budgeted expansion.
4. On relevant later turns, runtime rescans only when trigger conditions apply.
5. Before edits, agent is reminded to use injected context and then verify source.
### Flow 3: Compaction or artifact invalidation
1. Context compacts or root-pair artifacts change.
2. Runtime treats that as a relevant reinjection trigger.
3. Runtime rescans outgoing context.
4. If canonical root-pair block is absent, inject again.
### Flow 4: Strict-mode sensitive action
1. Agent approaches a sensitive edit or architectural claim.
2. Runtime checks whether the protocol path is present.
3. If not, runtime requires explicit bypass justification before proceeding.
## Acceptance criteria
- [ ] Pre-init behavior is hint-only and never injects fake map content
- [ ] Post-init behavior always guarantees root pair preload before budgeted expansion
- [ ] Budgeting supports both relative and absolute caps, with the smaller effective budget winning
- [ ] Default budget is 15% of active context window, capped at 100k tokens, with absolute-cap fallback when context-window metadata is unavailable
- [ ] Reinjection avoidance is based on actual outgoing-context scanning
- [ ] Mixed visibility is honored
- [ ] Four guidance modes exist with `strong` as default
- [ ] `strong` checks fire on relevant turns only
- [ ] `strict` adds bypass-justification semantics for missing protocol path on sensitive actions
- [ ] Extensive integration tests cover context scanning and reinjection behavior
@@ -0,0 +1,90 @@
# Tasks: Project Map Prompt Injection
## Status
| Field | Value |
|---|---|
| Phase | **Tasks** |
| Based on | [Design](design.md) |
| Next | Apply |
## Delivery slices
### Slice 1: Injection policy scaffold and config surface
**Scope**: mode/config scaffolding, pre-init hint behavior, canonical helper boundaries
**Review goal**: establish the policy control surface without yet wiring the full runtime pipeline
**Tasks**:
1. [ ] Add a shared prompt-injection policy helper/module
2. [ ] Add config support for injection mode, context-budget percent, and absolute cap using additive flat JSON keys compatible with existing `.pi-project-map.json` loading
3. [ ] Decide and document how existing `contextBudget` interacts with the new injection-budget knobs
4. [ ] Implement pre-init startup-hint behavior with no synthetic artifact injection
5. [ ] Define canonical marker/block construction for injected root-pair content
6. [ ] Add/update tests for config loading and pre-init hint behavior
### Slice 2: Root-pair preload and budgeted expansion
**Scope**: post-init preload, budget calculation, artifact selection under cap
**Review goal**: make automatic injection materially real after init
**Tasks**:
1. [ ] Implement guaranteed root-pair preload after init
2. [ ] Implement effective-budget calculation using percent + absolute cap with smaller-wins semantics
3. [ ] Implement active-model context-window discovery and explicit absolute-cap fallback when metadata is unavailable
4. [ ] Implement deterministic budgeted expansion beyond the root pair
5. [ ] Keep retrieval explicitly out of this injection path
6. [ ] Add/update tests for root-pair guarantee, context-window fallback, and budget-capped expansion
### Slice 3: Reinjection avoidance and relevant-turn checks
**Scope**: outgoing-context scanning, relevant-turn triggers, compaction/artifact invalidation
**Review goal**: avoid wasteful reinjection while preserving strong guidance
**Tasks**:
1. [ ] Implement canonical marker scanning over `event.messages`
2. [ ] Add fallback inspection of final provider payload when needed
3. [ ] Implement relevant-turn reinjection triggers for `strong` mode
4. [ ] Reinject after compaction and after root-pair artifact changes
5. [ ] Add/update integration tests for dedupe, reinjection suppression, and reinjection after invalidation
6. [ ] Include synthetic event-sequence coverage for edit-intent, architecture-sensitive reasoning, compaction, and artifact-change heuristics
### Slice 4: Mode semantics, visibility, and strict-path behavior
**Scope**: off/advisory/strong/strict semantics, mixed visibility, strict bypass behavior
**Review goal**: make the mode ladder operational and reviewable
**Tasks**:
1. [ ] Implement mode-specific behavior for `off`, `advisory`, `strong`, and `strict`
2. [ ] Define and enforce the spec meaning of a missing `protocol path` during sensitive actions
3. [ ] Keep startup hints user-visible and artifact injection agent-visible by default
4. [ ] Implement strict-mode explicit bypass-justification behavior for sensitive edits/architectural claims
5. [ ] Add/update integration tests for visibility, protocol-path detection, and mode differences
6. [ ] Verify that `strong` remains the default behavior
### Slice 5: Documentation and runtime alignment
**Scope**: docs, runtime guidance text, implementation notes
**Review goal**: align user-facing/runtime-facing guidance with the frozen spec
**Tasks**:
1. [ ] Update runtime guidance strings to reflect init-hint behavior, root-pair preload, trust boundary, and mode ladder
2. [ ] Update docs/skill guidance for the prompt-injection policy
3. [ ] Keep retrieval guidance separate from these docs or clearly reference it as separate
4. [ ] Document the opinionated default budget and configurability
5. [ ] Summarize integration-test expectations and known risks
## Acceptance checklist
- [ ] Before init, only a lightweight `project_map_init` hint is injected
- [ ] No synthetic map/index artifact content is injected before init
- [ ] After init, root `.pi-map.index.md` and root `.pi-map.md` are always guaranteed before budgeted expansion
- [ ] Expansion uses a hybrid cap with both relative and absolute knobs, smaller effective budget wins
- [ ] Default budget is 15% of active context window, capped at 100k tokens
- [ ] Reinjection avoidance is based on canonical marker scanning in actual outgoing context
- [ ] `strong` checks only relevant turns, not every trivial turn
- [ ] Modes `off`, `advisory`, `strong`, and `strict` are implemented with the agreed semantics
- [ ] Mixed visibility behavior is preserved
- [ ] Retrieval behavior remains separate from this specs implementation scope
- [ ] Extensive integration tests validate context scanning and reinjection behavior
- [ ] `npm run typecheck` passes
- [ ] `npm test` passes
- [ ] `npm run lint` passes
## Review workload note
This change mixes runtime hook behavior, prompt budgeting, context dedupe, visibility policy, and strict-mode enforcement. Keep delivery narrow and test-heavy. Avoid collapsing this into one oversized implementation slice.