From c6cab807d5decaab39b16d713177e13d3d8e33e2 Mon Sep 17 00:00:00 2001 From: can1357 Date: Thu, 30 Apr 2026 02:39:47 +0200 Subject: [PATCH] feat: compressed prompts slightly --- crates/pi-natives/src/tokens.rs | 4 +- .../coding-agent/src/prompts/tools/atom.md | 15 +- .../src/prompts/tools/exit-plan-mode.md | 44 +---- .../coding-agent/src/prompts/tools/task.md | 183 ++++-------------- .../src/prompts/tools/todo-write.md | 84 ++------ packages/coding-agent/src/tools/debug.ts | 4 +- .../coding-agent/src/tools/recipe/runner.ts | 1 - 7 files changed, 80 insertions(+), 255 deletions(-) diff --git a/crates/pi-natives/src/tokens.rs b/crates/pi-natives/src/tokens.rs index 03270451b..0b524c169 100644 --- a/crates/pi-natives/src/tokens.rs +++ b/crates/pi-natives/src/tokens.rs @@ -2,7 +2,7 @@ //! //! Two encodings are exposed: //! -//! - `O200kBase` — GPT-4o / o1 / GPT-5 (the modern OpenAI default). +//! - `O200kBase` — GPT-4o / o1 / GPT-5 (the modern `OpenAI` default). //! - `Cl100kBase` — GPT-3.5 / GPT-4 / older models. //! //! `o200k_base` is the default. Anthropic doesn't publish their tokenizer, so @@ -51,7 +51,7 @@ fn encoder(encoding: Option) -> &'static CoreBPE { /// /// Uses ordinary encoding (no special-token handling), which is the right /// choice for measuring user/model content rather than wire-protocol tokens. -/// Defaults to `o200k_base`; pass `Cl100kBase` for older OpenAI models. +/// Defaults to `o200k_base`; pass `Cl100kBase` for older `OpenAI` models. #[napi] pub fn count_tokens(input: Either>, encoding: Option) -> u32 { let bpe = encoder(encoding); diff --git a/packages/coding-agent/src/prompts/tools/atom.md b/packages/coding-agent/src/prompts/tools/atom.md index 65b25f61b..3e6c74502 100644 --- a/packages/coding-agent/src/prompts/tools/atom.md +++ b/packages/coding-agent/src/prompts/tools/atom.md @@ -28,9 +28,10 @@ Lid= blank the anchored line's content but KEEP the line (results in an em - Cursor-only ops (`^`, `$`, `@Lid`, `^Lid`) reposition without modifying. To insert anything you **MUST** follow them with `+TEXT` (or `+` for a blank). - TEXT in `+TEXT`, `Lid=TEXT`, and `\TEXT` is literal line content, INCLUDING leading whitespace. You **MUST NOT** trim or re-indent it. - Consecutive `+TEXT` ops produce consecutive lines in the order written. You **MUST NOT** separate them with a stray `+` unless you intend to insert a blank line. -- `Lid=TEXT` rewrites ONE line. To rewrite K adjacent lines, you **MUST** use `LidA..LidB=FIRST_LINE` followed immediately by `\NEXT_LINE` continuation lines. You **MUST** use bare `\` for blank replacement lines. +- `Lid=TEXT` rewrites ONE line. To rewrite K adjacent lines, you **MUST** use `LidA..LidB=FIRST_LINE` followed immediately by `\NEXT_LINE` continuation lines (canonical form for any block replacement). You **MUST** use bare `\` for blank replacement lines. - You **MUST** prefix every replacement continuation line with `\`, especially when the replacement line starts with edit syntax characters such as `#`, `+`, `-`, `@`, `$`, `^`, `!`, or a Lid-shaped token. - `\TEXT` **MUST** appear only immediately after an active `Lid=…` or `LidA..LidB=…` replacement. It **MUST NOT** be used as a general insert operator. +- A `\TEXT` line **MUST** be the immediate continuation of a `Lid=…` or `LidA..LidB=…` op on the line above (or another `\` line rooted in one). If the line above is `+TEXT`, a bare Lid, a cursor op, or whitespace, the `\` is invalid and the tool will not interpret it as part of a replacement. - The legacy `-LidA..LidB` + `+TEXT…` block-rewrite form also works. - To insert ABOVE a line, you **MUST** use `^Lid` then `+TEXT`. To insert above line 1, you **MUST** use `^` (BOF) then `+TEXT`. To insert below a line, you **MUST** use `@Lid` then `+TEXT`. - Multiple `---PATH` sections **MAY** appear in one input; each section is applied in order. @@ -82,6 +83,16 @@ Lid= blank the anchored line's content but KEEP the line (results in an em \ return (name || DEF).trim().toUpperCase(); \} +# Replace a block with a longer multi-line block, including blank lines (canonical form for refactors) +---a.ts +{{hrefr 3}}..{{hrefr 6}}=/** Format a display label, falling back to DEF when empty. */ +\export function label(name: string): string { +\ const clean = (name || DEF).trim(); +\ +\ if (clean.length === 0) return DEF; +\ return clean.toUpperCase(); +\} + # Insert ABOVE a line ---a.ts ^{{hrefr 5}} @@ -128,6 +139,8 @@ $ - Current/added preview lines include fresh `LINE+hash|content` anchors. Removed preview lines show deleted content and **MUST NOT** be reused as anchors. - You **MUST** emit only lines that change. You **MUST NOT** echo unchanged context; the anchor implies position. - You **MUST NOT** write `Lid=`; the tool reports a no-op (no change applied). Emit `Lid=TEXT` only when TEXT differs. +- A line of the form `Lid|content` (a Lid, then `|`, then text, with NO leading `+`/`-`/`^`/`@`/`\`/`=`/`..`) is **FORBIDDEN**. That shape only appears in `read`/`grep` output as an anchor for *you*; it is never an edit op. If you copy a `Lid|content` line verbatim from a read into a patch, you have made an error — every edit op must start with `+`, `-`, `^`, `@`, `\`, `$`, `!`, or a Lid immediately followed by `=` or `..`. +- To replace a contiguous block with new content, the canonical form is `LidA..LidB=FIRST_LINE` + `\NEXT_LINE…`. You **MUST NOT** write the old block and then the new block — that is unified-diff thinking and the tool does not understand it. If you find yourself emitting pre-image lines (with or without operators) before your new content, STOP and rewrite the section as a single range-replace. - TEXT after `=`, `+`, or `\` includes leading whitespace verbatim. You **MUST NOT** trim or re-indent it. - This is NOT unified diff. You **MUST NOT** write `@@` headers, `-OLD`/`+NEW` pairs, context lines, or `+Lid|…` (bad: `+5th|new text`; good: `5th=new text`). - You **MUST NOT** split `Lid=TEXT` across two physical lines. diff --git a/packages/coding-agent/src/prompts/tools/exit-plan-mode.md b/packages/coding-agent/src/prompts/tools/exit-plan-mode.md index 1b03bd837..a3b1b8319 100644 --- a/packages/coding-agent/src/prompts/tools/exit-plan-mode.md +++ b/packages/coding-agent/src/prompts/tools/exit-plan-mode.md @@ -1,40 +1,6 @@ -Signals plan completion, requests user approval, and provides the final plan title for handoff. +Submits a finalized implementation plan for user approval. - -Use when: -- Plan written to `local://PLAN.md` -- No unresolved questions about requirements or approach -- Ready for user review and approval - - - -- You **MUST** write plan to plan file BEFORE calling this tool -- Tool reads plan from file—does not take plan content as parameter -- You **MUST** provide a `title` argument for the final plan artifact (example: `WP_MIGRATION_PLAN`) -- `.md` is optional in `title`; it is appended automatically when omitted -- User sees plan contents when reviewing - - - -Presents plan to user for approval. If approved, plan mode exits with full tool access restored and the plan is renamed to `local://.md`. -</output> - -<examples> -# Ready -Plan complete at local://PLAN.md, no open questions. -→ Call `exit_plan_mode` with `{ "title": "WP_MIGRATION_PLAN" }` -# Unclear -Unsure about auth method (OAuth vs JWT). -→ Use `ask` first to clarify, then call `exit_plan_mode` -</examples> - -<avoid> -- **MUST NOT** call before plan is written to file -- **MUST NOT** omit `title` -- **MUST NOT** use `ask` to request plan approval (this tool does that) -- **MUST NOT** call after pure research tasks (no implementation planned) -</avoid> - -<critical> -You **MUST** only use when planning implementation steps. Research tasks (searching, reading, understanding) do not need this tool. -</critical> +Write the plan to `local://PLAN.md` first, then call this with `title` (e.g. `WP_MIGRATION_PLAN`); on approval the file is renamed to `local://<title>.md` and full tool access is restored. +- Use only after planning implementation steps; not for pure research. +- **MUST NOT** call before the plan file exists. +- **MUST NOT** use `ask` to request plan approval — this tool does that. diff --git a/packages/coding-agent/src/prompts/tools/task.md b/packages/coding-agent/src/prompts/tools/task.md index a417cd637..85a9be7e0 100644 --- a/packages/coding-agent/src/prompts/tools/task.md +++ b/packages/coding-agent/src/prompts/tools/task.md @@ -1,167 +1,54 @@ Launches subagents to parallelize workflows. {{#if asyncEnabled}} -- Use `read jobs://` to inspect state; `read jobs://<job_id>` for detail. -- Use the `job` tool (with `poll`) to wait until completion. You **MUST NOT** poll `read jobs://` in a loop. +- `read jobs://` for state, `read jobs://<id>` for detail. +- Use `job` (with `poll`) to wait. **MUST NOT** poll `read jobs://` in a loop. {{/if}} -{{#if defaultMode}} -Current input mode: `default`. Shared `context` and custom task-call `schema` are available. -{{/if}} -{{#if schemaFreeMode}} -Current input mode: `schema-free`. Shared `context` is available; custom task-call `schema` is disabled. For structured output, rely on the agent definition or inherited session schema. -{{/if}} -{{#if independentMode}} -Current input mode: `independent`. Shared `context` and custom `schema` are both disabled. Every assignment must stand on its own. -{{/if}} - -{{#if contextEnabled}} -Subagents lack your conversation history. Every decision, file content, and user requirement they need **MUST** be explicit in `context` or `assignment`. -{{else}} -Subagents lack your conversation history. Every decision, file content, and user requirement they need **MUST** be explicit in each task `assignment`. -{{/if}} +Subagents have no access to your conversation history. Every fact, file path, and decision they need **MUST** be explicit in {{#if contextEnabled}}`context` or `assignment`{{else}}each `assignment`{{/if}}. <parameters> -- `agent`: Agent type for all tasks. - - `.id`: CamelCase, max 32 chars - - `.description`: UI display only — subagent never sees it - - `.assignment`: Complete self-contained instructions. One-liners PROHIBITED; missing acceptance criteria = too vague. -{{#if contextEnabled}} -- `context`: Shared background prepended to every assignment. Session-specific info only. -{{/if}} -{{#if customSchemaEnabled}} -- `schema`: JSON-encoded JTD schema for expected output. Format lives here — **MUST NOT** be duplicated in assignments. -{{/if}} -- `tasks`: Tasks to execute in parallel. -{{#if isolationEnabled}} -- `isolated`: Run in isolated environment; returns patches. Use when tasks edit overlapping files. -{{/if}} +- `agent`: agent type for all tasks +- `tasks`: tasks to execute in parallel + - `.id`: CamelCase, ≤32 chars + - `.description`: UI label only — subagent never sees it + - `.assignment`: complete self-contained instructions; one-liners and missing acceptance criteria are PROHIBITED +{{#if contextEnabled}}- `context`: shared background prepended to every assignment; session-specific only{{/if}} +{{#if customSchemaEnabled}}- `schema`: JTD schema for expected structured output (do not put format rules in assignments){{/if}} +{{#if isolationEnabled}}- `isolated`: run in isolated env; use when tasks edit overlapping files{{/if}} </parameters> -<critical> -{{#if contextEnabled}} -- **MUST NOT** duplicate shared constraints across assignments — put them in `context` once. -{{else}} -- Every `assignment` must repeat any constraints, reference paths, and acceptance criteria it needs — there is no shared `context` field. -{{/if}} -- **MUST NOT** tell tasks to run project-wide build/test/lint. Parallel agents share the working tree; each task edits, stops. Caller verifies after all complete. -- For large payloads (traces, JSON blobs), write to `local://<path>` and pass the path in {{#if contextEnabled}}`context`{{else}}the relevant `assignment`{{/if}}. -- Prefer `task` agents that investigate **and** edit in one pass. Launch a dedicated read-only discovery step only when affected files are genuinely unknown. -</critical> - -<scope> -Each task: **at most 3–5 files**. Globs, "update all", or package-wide scope = too broad. Enumerate files explicitly and fan out to a cluster of agents. -</scope> +<rules> +- **MUST NOT** assign tasks to run project-wide build/test/lint. Caller verifies after the batch. +- Each task: ≤3–5 explicit files. No globs, no "update all", no package-wide scope. Fan out to a cluster instead. +- Pass large payloads via `local://<path>` URIs, not inline. +{{#if contextEnabled}}- Put shared constraints in `context` once; do not duplicate across assignments.{{/if}} +- Prefer agents that investigate **and** edit in one pass; only spin a read-only discovery step when affected files are genuinely unknown. +</rules> <parallelization> -**Test:** Can task B produce correct output without seeing A's result? Yes → parallel. No → sequential. - -|Sequential first|Then|Reason| -|---|---|---| -|Types/interfaces|Consumers|Need contract| -|API exports|Callers|Need signatures| -|Core module|Dependents|Import dependency| -|Schema/migration|App logic|Schema dependency| - -**Safe to parallelize:** independent modules, isolated file-scoped refactors, tests for existing code. +Test: can task B run correctly without seeing A's output? If no, sequence A → B. +Sequential when one task produces a contract (types, API, schema, core module) the other consumes. +Parallel when tasks touch disjoint files or are independent refactors/tests. </parallelization> -<templates> {{#if contextEnabled}} -**context:** -``` -## Goal ← one sentence: what the batch accomplishes -## Non-goals ← what tasks must not touch -## Constraints ← MUST/MUST NOT rules and session decisions -## API Contract ← exact types/signatures if tasks share an interface (omit if N/A) -## Acceptance ← definition of done; build/lint runs AFTER all tasks complete -``` -{{else}} -No shared `context` field exists in this mode. Fold goal, non-goals, constraints, and acceptance criteria into each `assignment`. -{{/if}} -**assignment:** -``` -## Target ← exact file paths; named symbols; explicit non-goals -## Change ← step-by-step what to add/remove/rename; patterns/APIs to use -## Edge Cases ← tricky inputs; existing behavior that must survive -## Acceptance ← observable result proving the task is done; no project-wide commands -``` -</templates> - -<checklist> -Before invoking: -{{#if contextEnabled}} -- `context` contains only session-specific info -{{else}} -- Every `assignment` includes its own goal, constraints, and acceptance criteria (no shared context) -{{/if}} -- Every `assignment` follows the template; no one-liners; edge cases covered -- Tasks are truly parallel — you can articulate why none depends on another's output -- File paths are explicit; no globs -{{#if customSchemaEnabled}} -- `schema` is set if you expect structured output -{{else}} -- Do not pass a custom task-call `schema` in this mode -{{/if}} -</checklist> - -{{#if contextEnabled}} -<example label="Rename exported symbol + update all call sites"> -Two tasks with non-overlapping file sets — demonstrates scope partitioning. - -<context> -## Goal -Rename `parseConfig` → `loadConfig` in `src/config/parser.ts` and all callers. -## Non-goals -No behavior or signature changes; rename only. -## Acceptance (global) -Caller runs `bun check:ts` after both tasks complete. Tasks must NOT run it. -</context> -<tasks> - <task name="RenameExport"> - <assignment> -## Target -- `src/config/parser.ts`: function `parseConfig` -- If `src/config/index.ts` re-exports it, update the re-export -- Non-goals: do not touch callers or tests - -## Change -- Rename `parseConfig` → `loadConfig` (declaration + any JSDoc references) - -## Edge Cases -- Rename all overload signatures if overloaded -- Internal helpers like `_parseConfigValue` are different symbols — leave untouched -- Do not add a backwards-compat alias - -## Acceptance -- `parseConfig` no longer appears as a top-level export in `parser.ts` - </assignment> - </task> - <task name="UpdateCallers"> - <assignment> -## Target -- `src/cli/init.ts`, `src/server/bootstrap.ts`, `src/worker/index.ts` -- Non-goals: do not touch `src/config/parser.ts` or `src/config/index.ts` - -## Change -- Replace `import { parseConfig }` → `import { loadConfig }` -- Replace every call site `parseConfig(` → `loadConfig(` -- For `import * as cfg` users, update `cfg.parseConfig` property access - -## Edge Cases -- String literals containing "parseConfig" (logs, comments) are documentation — leave them -- If a file re-exports to an external package boundary, keep the old name via `export { loadConfig as parseConfig }` with a `// TODO: remove after next major` comment - -## Acceptance -- No bare `parseConfig` identifier remains in the three target files - </assignment> - </task> -</tasks> -</example> +<context-fmt> +# Goal ← one sentence: what the batch accomplishes +# Constraints ← **MUST**/**MUST NOT** rules and session decisions +# Contract ← exact types/signatures if tasks share an interface +</context-fmt> {{/if}} +<assignment-fmt> +# Target ← exact files and symbols; explicit non-goals +# Change ← step-by-step add/remove/rename; APIs and patterns +# Acceptance ← observable result; no project-wide commands +</assignment-fmt> + +<agents> {{#list agents join="\n"}} -### Agent: {{name}} -**Tools:** {{default (join tools ", ") "All"}} +# {{name}} {{description}} {{/list}} +</agents> diff --git a/packages/coding-agent/src/prompts/tools/todo-write.md b/packages/coding-agent/src/prompts/tools/todo-write.md index 82fa4f948..eb0c93689 100644 --- a/packages/coding-agent/src/prompts/tools/todo-write.md +++ b/packages/coding-agent/src/prompts/tools/todo-write.md @@ -1,69 +1,33 @@ -Manages a phased task list through an `ops` array of flat operations. -The next pending task is auto-promoted to `in_progress` after completing the current one. +Manages a phased task list. Pass `ops`: a flat array of operations. +The next pending task is auto-promoted to `in_progress` after each completion. -<protocol> -## Shape +## Operations -Pass an object with an `ops` array: - -```ts -{ - ops: [ - { op: "replace", phases: [...] }, - { op: "start", task: "task-3" }, - { op: "done", phase: "Implementation" }, - { op: "rm" }, - { op: "drop", task: "task-9" }, - { op: "append", phase: "Implementation", items: [{ id: "task-10", label: "Run tests" }] }, - ], -} -``` - -## Operation fields - -|Field|Type|When to use| +|`op`|Required fields|Effect| |---|---|---| -|`op`|string|Required. One of `replace`, `start`, `done`, `rm`, `drop`, `append`, `note`| -|`task`|string|Task id for `start`, or a task target for `done` / `rm` / `drop`| -|`phase`|string|Phase target for `done` / `rm` / `drop`, or append destination for `append`| -|`items`|{id, label}[]|Required for `append`. If the phase does not exist, it is created at the end| -|`phases`|Phase[]|Only for `replace`. Keeps initial phased setup available for harness bootstrap and full restructures| -|`text`|string|Required for `note`. The note text appended to `task.notes` (which is a list, joined with newlines on render)| +|`replace`|`phases`|Replace the full list (initial setup, full restructure)| +|`start`|`task`|Set task to `in_progress`| +|`done`|`task` or `phase` (or neither = all)|Mark completed| +|`drop`|`task` or `phase` (or neither = all)|Mark abandoned| +|`rm`|`task` or `phase` (or neither = all)|Remove| +|`append`|`phase`, `items: {id, label}[]`|Append tasks; creates phase if missing| +|`note`|`task`, `text`|Append a note to `task.notes`. Only use to leave reminders for future-you.| -## Semantics -- `start`: requires `task`; sets that task to `in_progress` -- `done`: marks one task, one phase, or all tasks completed -- `rm`: removes one task, one phase's tasks, or all tasks -- `drop`: marks one task, one phase, or all tasks abandoned -- `append`: appends `items` to `phase`; creates the phase if missing -- `replace`: replaces the full todo list -- `note`: append `text` as a new note attached to `task`. Notes are append-only context the user added; they only render to you when the task is `in_progress`. Other tasks display only a `+N` marker. Use this when you want to leave a follow-up reminder for yourself when you reach a later task. - -If `done`, `rm`, or `drop` omits both `task` and `phase`, it applies to all tasks. - -## Task Anatomy -- `label`: Short label (5-10 words). What is being done, not how. -- `replace` task `content` should stay short and specific. - -## Phase Anatomy -- `name`: Short, human-readable noun phrase (1-3 words). Capitalize naturally. -- Always prefix with a roman-numeral ordinal (`I.`, `II.`, `III.`, `IV.`, …) to convey ordering — e.g. `I. Foundation`, `II. Auth`, `III. Routing`. Single-phase plans use `I.` too. -- You **MUST NOT** use snake_case, `Phase1_*`, arabic numerals (`1.`), or letter prefixes (`A.`) — they render as ugly identifiers. +## Anatomy +- **Task `label`**: 5–10 words, what is being done, not how. +- **Phase `name`**: short noun phrase prefixed with a roman numeral — `I. Foundation`, `II. Auth`, `III. Verification`. Single-phase plans still use `I.`. Never use snake_case, arabic numerals, or letter prefixes. ## Rules - Mark tasks done immediately after finishing — never defer. -- Complete phases in order — do not skip ahead while earlier ones are pending. -- On blockers, append a new task to the active phase. +- Complete phases in order. +- On blockers, `append` a new task to the active phase. - Keep ids stable once introduced. -</protocol> -<conditions> -Create a todo list when: -1. Task requires 3+ distinct steps -2. User explicitly requests one -3. User provides a set of tasks to complete -4. New instructions arrive mid-task — capture before proceeding -</conditions> +## When to create a list +- Task requires 3+ distinct steps +- User explicitly requests one +- User provides a set of tasks to complete +- New instructions arrive mid-task — capture before proceeding <examples> # Initial setup (multi-phase) @@ -81,9 +45,3 @@ Create a todo list when: # Append tasks to a phase `{"ops":[{"op":"append","phase":"II. Auth","items":[{"id":"task-8","label":"Handle retries"},{"id":"task-9","label":"Run tests"}]}]}` </examples> - -<avoid> -- Single-step tasks — act directly -- Conversational or informational requests -- Tasks completable in under 3 trivial steps -</avoid> diff --git a/packages/coding-agent/src/tools/debug.ts b/packages/coding-agent/src/tools/debug.ts index b30b7cb61..73519202f 100644 --- a/packages/coding-agent/src/tools/debug.ts +++ b/packages/coding-agent/src/tools/debug.ts @@ -93,7 +93,9 @@ const debugSchema = Type.Object({ condition: Type.Optional(Type.String({ description: "breakpoint condition" })), hit_condition: Type.Optional(Type.String()), expression: Type.Optional(Type.String({ description: "expression to evaluate" })), - context: Type.Optional(Type.String({ description: "evaluate context: watch | repl | hover | variables | clipboard" })), + context: Type.Optional( + Type.String({ description: "evaluate context: watch | repl | hover | variables | clipboard" }), + ), frame_id: Type.Optional(Type.Number()), scope_id: Type.Optional(Type.Number({ description: "scope variables reference" })), variable_ref: Type.Optional(Type.Number({ description: "variable reference" })), diff --git a/packages/coding-agent/src/tools/recipe/runner.ts b/packages/coding-agent/src/tools/recipe/runner.ts index 0b5e0bf9a..253ba5d64 100644 --- a/packages/coding-agent/src/tools/recipe/runner.ts +++ b/packages/coding-agent/src/tools/recipe/runner.ts @@ -46,7 +46,6 @@ interface PromptTaskModel { const PROMPT_TASK_LIMIT = 20; - interface PromptRunnerModel { id: string; label: string;