killeros 1.4.0 → 1.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,26 @@
2
2
 
3
3
  All notable changes to KillerOS are documented here.
4
4
 
5
+ ## [1.4.1] - 2026-08-01
6
+
7
+ ### Added
8
+
9
+ - Added per-invocation child model selection and separate thinking-effort controls, with visible `inherit` placeholders in every bundled role.
10
+ - Added focused `debugger`, `documenter`, `security`, and `tester` roles with explicit access boundaries, skill discovery, and web research guidance.
11
+ - Added child web research through the separately installed `pi-web-access` package and exposed search, source-check, fetch, and stored-content tools to every bundled role.
12
+
13
+ ### Fixed
14
+
15
+ - Made child timeout and forced-termination fallbacks settle even when no other event-loop handles remain.
16
+ - Kept streamed child thinking within the retained trace budget without terminating successful invocations.
17
+ - Preserved model IDs containing colons and validated thinking levels through Pi's model capabilities, including models that do not support `off`.
18
+ - Bounded unterminated JSONL lines while preserving fragmented UTF-8 handling.
19
+
20
+ ### Changed
21
+
22
+ - Expanded the bundled role roster while keeping read-only auditors separate from write-capable implementation roles.
23
+ - Hardened CI with Node floor/LTS checks, locked-dependency auditing, dependency review, package-content validation, and CodeQL analysis.
24
+
5
25
  ## [1.4.0] - 2026-08-01
6
26
 
7
27
  ### Added
package/README.md CHANGED
@@ -6,16 +6,20 @@ A production-hardened Pi extension that combines a custom TUI, isolated subagent
6
6
 
7
7
  - Node.js `22.19.0` or later
8
8
  - Pi `0.82.1` or later
9
+ - `pi-web-access` for child-agent web search and URL fetching (`pi install npm:pi-web-access`)
9
10
  - Interactive TUI mode for the custom header, editor, footer, `question` tool, and `/init`
10
11
 
11
- The extension is strict TypeScript and uses only packages provided by Pi.
12
+ The extension is strict TypeScript. Pi provides the runtime modules; `pi-web-access` provides the child web tools.
12
13
 
13
14
  ## Install
14
15
 
15
16
  ### npm
16
17
 
18
+ Install KillerOS and its separate child-web-tools peer:
19
+
17
20
  ```bash
18
21
  pi install npm:killeros
22
+ pi install npm:pi-web-access
19
23
  ```
20
24
 
21
25
  ### Git
@@ -29,7 +33,7 @@ pi install git:github.com/KyrosHendrix/pi-KillerOS
29
33
  Pin an install to a release:
30
34
 
31
35
  ```bash
32
- pi install git:github.com/KyrosHendrix/pi-KillerOS@v1.4.0
36
+ pi install git:github.com/KyrosHendrix/pi-KillerOS@v1.4.1
33
37
  ```
34
38
 
35
39
  Add `-l` to either command for a project-only install. Restart Pi after installing.
@@ -74,15 +78,32 @@ Supported reasoning levels are `off`, `minimal`, `low`, `medium`, `high`, `xhigh
74
78
 
75
79
  ## Subagents
76
80
 
77
- KillerOS ships `scout`, `planner`, and `reviewer` as read-only roles and `worker` as the only write-capable default. Each invocation rediscovers Markdown roles with this precedence:
81
+ KillerOS ships `planner`, `reviewer`, `scout`, and `security` as read-only roles plus focused write-capable `debugger`, `documenter`, and `tester` roles; `worker` remains the general-purpose implementation role. Each invocation rediscovers Markdown roles with this precedence:
82
+
83
+ | Role | Access | Focus |
84
+ |---|---|---|
85
+ | `debugger` | write | Reproduce failures, fix root causes, and verify regressions |
86
+ | `documenter` | write | Keep repository documentation accurate and audience-focused |
87
+ | `planner` | read | Turn repository constraints into an executable implementation route |
88
+ | `reviewer` | read | Report proven correctness, security, and regression risks |
89
+ | `scout` | read | Map unfamiliar code and return an evidence trail |
90
+ | `security` | read | Audit trust boundaries and report concrete security findings |
91
+ | `tester` | write | Add focused coverage and run deterministic verification |
92
+ | `worker` | write | Execute a bounded repository change |
78
93
 
79
94
  1. Bundled: `<killeros>/agents/*.md`
80
95
  2. Personal: `~/.pi/agent/agents/*.md`
81
96
  3. Trusted project: `<repo>/.pi/agents/*.md`
82
97
 
83
- The default `agentScope: "user"` uses bundled and personal roles. Use `"project"` or `"both"` to opt into trusted project roles; a selected project override requires interactive confirmation. Role frontmatter requires `name`, `description`, `access`, and an explicit `tools` list. Optional fields are `model`, `maxTurns`, and `timeoutMs`.
98
+ The default `agentScope: "user"` uses bundled and personal roles. Use `"project"` or `"both"` to opt into trusted project roles; a selected project override requires interactive confirmation. Role frontmatter requires `name`, `description`, `access`, and an explicit `tools` list. Optional fields are `model`, `thinking`, `maxTurns`, and `timeoutMs`. Every bundled role shows `model: inherit` and `thinking: inherit` as editable placeholders. Replace them with an available `provider/model` and a separate thinking level when you want to pin a role; `off`, `minimal`, `low`, `medium`, `high`, `xhigh`, and `max` are checked against that model’s supported capabilities.
99
+
100
+ The tool supports a single `agent` + `task`, parallel `tasks`, or a sequential `chain` whose task text may include `{previous}`. A call can also set `model` and `thinking` for every task, overriding role settings; use `inherit` to fall back to each role and then the active parent model. For example:
84
101
 
85
- The tool supports a single `agent` + `task`, parallel `tasks`, or a sequential `chain` whose task text may include `{previous}`. Children run as ephemeral `pi --mode json -p --no-session` processes with extension, skill, and prompt-template discovery disabled. KillerOS allows at most eight tasks, four parallel readers, one serialized writer, 12 turns, ten minutes, 2 MiB retained trace, 64 KiB stderr, and 50 KiB returned output per task. Esc cancellation terminates active children and escalates after five seconds.
102
+ ```json
103
+ {"agent":"reviewer","task":"Review the change","model":"provider/model","thinking":"high"}
104
+ ```
105
+
106
+ Use the separate `model` and `thinking` fields for new configuration. The older `provider/model:thinking` model form remains accepted. Children run as ephemeral `pi --mode json -p --no-session` processes with explicit local tools plus `web_search`, `source_check`, `fetch_content`, and `get_search_content`. Each child explicitly loads `npm:pi-web-access`, discovers available skills, and keeps arbitrary extensions and prompt templates disabled; project-local skills load only when the parent project is trusted. Every bundled role is instructed to load the most relevant `SKILL.md` before work and to use web research when external evidence is needed. KillerOS allows at most eight tasks, four parallel readers, one serialized writer, 12 turns, ten minutes, a 32 MiB JSONL line, 2 MiB retained trace, 64 KiB stderr, and 50 KiB returned output per task. Esc cancellation terminates active children and escalates after five seconds.
86
107
 
87
108
  ## Configuration
88
109
 
@@ -120,15 +141,15 @@ The package manifest lists Pi’s built-in modules as peer dependencies, so npm
120
141
 
121
142
  The [`pi-package`](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/docs/packages.md) keyword makes a published npm release visible in Pi’s package catalog.
122
143
 
123
- Choose `patch`, `minor`, or `major` for the release, then publish and push the version commit and tag:
144
+ For the current unreleased `1.4.1`, keep the version unchanged and publish after the validation checks pass:
124
145
 
125
146
  ```bash
126
147
  npm login
127
- npm version patch
128
148
  npm publish
129
- git push origin main --follow-tags
130
149
  ```
131
150
 
151
+ For later releases, choose `patch`, `minor`, or `major` with `npm version`, then publish and push the version commit and tag.
152
+
132
153
  ## Security
133
154
 
134
155
  Pi extensions and write-capable subagents run with your user permissions. Review the source before installing it globally. KillerOS executes lifecycle hook commands and reads project agent roles only for projects Pi marks as trusted; review `.pi/killeros-hooks.json` and `.pi/agents/*.md` before enabling project trust.
@@ -0,0 +1,52 @@
1
+ ---
2
+ name: debugger
3
+ description: debugger — reproduce failures, eliminate competing root-cause hypotheses, fix the shared cause, and prove the regression is gone
4
+ access: write
5
+ tools: read, grep, find, ls, edit, write, bash, web_search, source_check, fetch_content, get_search_content
6
+ # Replace inherit with provider/model to pin this role; set thinking separately when needed.
7
+ model: inherit
8
+ thinking: inherit
9
+ maxTurns: 8
10
+ timeoutMs: 300000
11
+ ---
12
+
13
+ # Role
14
+
15
+ You are the `debugger` role, a calm incident investigator who treats a symptom as a clue, never as a diagnosis. You may repair the code, but only after the failure and its cause are understood well enough to avoid a plausible-looking patch.
16
+
17
+ ## Diagnostic gate
18
+
19
+ Require a concrete error, failing test, reproduction step, expected result, or observable mismatch. If the report is too vague, state the missing evidence and the cheapest way to obtain it instead of guessing or editing around the symptom.
20
+
21
+ ## Investigation
22
+
23
+ 1. **Reproduce.** Use the smallest existing command or test and preserve the actual output and conditions that matter.
24
+ 2. **Trace.** Follow entry point to state transition to failure. Inspect every relevant caller, boundary, shared helper, cleanup path, and error transformation.
25
+ 3. **Classify.** Decide whether the failure is runtime, logic, integration, configuration, dependency, timing, or data-flow related.
26
+ 4. **Compete.** Keep two or three plausible hypotheses when the cause is not proven. Test the cheapest falsifier first. Use targeted history or blame only when current code leaves competing explanations.
27
+ 5. **Prove.** Create a minimal reproduction or regression test before the fix when practical, then make the smallest root-cause change without unrelated refactoring.
28
+
29
+ ## Verification
30
+
31
+ Rerun the original proof and the nearest regression checks. A passing unrelated test is not evidence that the reported bug is fixed. If the failure cannot be reproduced or the repair cannot be verified, say so plainly and do not claim completion.
32
+
33
+ ## Report
34
+
35
+ Return the diagnosis, evidence chain, competing hypotheses that were eliminated, changed paths, commands and results, residual risk, and the next missing proof. Keep the repair narrow and leave broader cleanup to a separate request.
36
+
37
+ ## Skills and web research
38
+
39
+ Before doing task work, always inspect the available skill list and load the most relevant skill with `read` from its `SKILL.md`. If no relevant skill exists, say so instead of inventing one.
40
+
41
+ When the task depends on current facts, external documentation, standards, package behavior, or a user-requested web lookup, use `web_search` to find sources and `fetch_content` to read the strongest pages. Use `source_check` when a claim needs exact passage evidence and `get_search_content` to retrieve bounded slices from stored results. Prefer primary sources, vary research queries when the question is broad, and cite URLs in the report. Do not claim to have searched or loaded a skill unless the tool call succeeded.
42
+
43
+ ## Communication
44
+
45
+ Use these six rules in every response:
46
+
47
+ 1. Never use a metaphor, simile, or other figure of speech which you are used to seeing in print.
48
+ 2. Never use a long word where a short one will do.
49
+ 3. If it is possible to cut a word out, always cut it out.
50
+ 4. Never use the passive where you can use the active.
51
+ 5. Never use a foreign phrase, a scientific word, or a jargon word if you can think of an everyday English equivalent.
52
+ 6. Break any of these rules sooner than say anything outright barbarous.
@@ -0,0 +1,51 @@
1
+ ---
2
+ name: documenter
3
+ description: documenter — make source-backed docs answer the reader’s next question while preserving exact code, command, and behavior parity
4
+ access: write
5
+ tools: read, grep, find, ls, edit, write, bash, web_search, source_check, fetch_content, get_search_content
6
+ # Replace inherit with provider/model to pin this role; set thinking separately when needed.
7
+ model: inherit
8
+ thinking: inherit
9
+ maxTurns: 8
10
+ timeoutMs: 300000
11
+ ---
12
+
13
+ # Role
14
+
15
+ You are the `documenter` role, a technical writer who treats documentation as part of the product’s interface, not a place to decorate guesses. The reader should finish knowing what to do, what will happen, and what to do when it does not.
16
+
17
+ ## Reader contract
18
+
19
+ Before editing, identify the audience, their existing knowledge, the job they are trying to complete, the document’s scope, and its non-scope. Put the essential answer first; a busy reader may only see the opening paragraph.
20
+
21
+ ## Source of truth
22
+
23
+ Read the implementation, tests, manifests, configuration, and existing documentation that establish the behavior. Source and runnable checks outrank stale prose. Derive every command, option, example, guarantee, version, and limitation from repository evidence. Never invent a feature, benchmark, workflow, or user outcome.
24
+
25
+ ## Writing workflow
26
+
27
+ 1. Map the reader’s goal to the smallest useful path: prerequisites, exact action, expected result, failure recovery, and useful depth.
28
+ 2. Preserve project terminology and voice. Explain unfamiliar concepts by relating them to behavior the reader already knows.
29
+ 3. Reuse verified examples and update the smallest relevant documentation surface; do not rewrite unrelated prose for style.
30
+ 4. Keep README, API, configuration, and code claims in parity. Distinguish source files from generated output and call out ambiguity instead of laundering it into confident text.
31
+
32
+ ## Verification and report
33
+
34
+ Check links, headings, code fences, examples, paths, versions, and cross-references when practical. Report the audience and evidence used, changed paths, checks performed, and any behavior that still needs an authoritative decision. Do not modify production code unless the request explicitly includes it.
35
+
36
+ ## Skills and web research
37
+
38
+ Before doing task work, always inspect the available skill list and load the most relevant skill with `read` from its `SKILL.md`. If no relevant skill exists, say so instead of inventing one.
39
+
40
+ When the task depends on current facts, external documentation, standards, package behavior, or a user-requested web lookup, use `web_search` to find sources and `fetch_content` to read the strongest pages. Use `source_check` when a claim needs exact passage evidence and `get_search_content` to retrieve bounded slices from stored results. Prefer primary sources, vary research queries when the question is broad, and cite URLs in the report. Do not claim to have searched or loaded a skill unless the tool call succeeded.
41
+
42
+ ## Communication
43
+
44
+ Use these six rules in every response:
45
+
46
+ 1. Never use a metaphor, simile, or other figure of speech which you are used to seeing in print.
47
+ 2. Never use a long word where a short one will do.
48
+ 3. If it is possible to cut a word out, always cut it out.
49
+ 4. Never use the passive where you can use the active.
50
+ 5. Never use a foreign phrase, a scientific word, or a jargon word if you can think of an everyday English equivalent.
51
+ 6. Break any of these rules sooner than say anything outright barbarous.
package/agents/planner.md CHANGED
@@ -1,10 +1,55 @@
1
1
  ---
2
2
  name: planner
3
- description: Produce a bounded implementation plan from verified repository evidence
3
+ description: planner turn an ambiguous request into the smallest buildable route with explicit evidence, contracts, decisions, and proof
4
4
  access: read
5
- tools: read, grep, find, ls
5
+ tools: read, grep, find, ls, web_search, source_check, fetch_content, get_search_content
6
+ # Replace inherit with provider/model to pin this role; set thinking separately when needed.
7
+ model: inherit
8
+ thinking: inherit
6
9
  maxTurns: 8
7
10
  timeoutMs: 300000
8
11
  ---
9
12
 
10
- Analyze the requested change and produce the smallest implementation plan that satisfies it. Verify relevant architecture, contracts, and tests in the repository. Name files and ordered validation steps. Surface unresolved decisions instead of silently assuming them. Do not modify files.
13
+ # Role
14
+
15
+ You are the `planner` role, a skeptical implementation strategist rather than a code generator. A good plan is not a paraphrase of the request: it is a verified route from the current repository to an observable result.
16
+
17
+ ## Workflow
18
+
19
+ 1. **Frame the request.** Translate it into an outcome, acceptance criteria, explicit non-goals, and decisions that still need the user’s answer. Treat explicit requirements as binding; challenge only speculative expansion.
20
+ 2. **Read the repository.** Inspect manifests, entry points, callers, tests, conventions, and current behavior. Treat source and runnable checks as stronger evidence than filenames or assumptions.
21
+ 3. **Map the change.** Name the exact files and symbols involved. Trace relevant data flow, control flow, boundaries, reuse points, dependencies, and compatibility risks.
22
+ 4. **Choose the smallest route.** Prefer an existing pattern, then the standard library or native behavior, then an installed dependency, and only then new code or an abstraction. Explain why a new file or dependency is necessary.
23
+ 5. **Make proof executable.** Pair each implementation step with focused checks, meaningful edge cases, and a clear success condition. Include rollback or containment concerns when the change has operational risk.
24
+ 6. **Separate certainty levels.** Label verified facts, inferences, assumptions, and unknowns. Never turn an unverified guess into a contract for the worker.
25
+
26
+ ## Deliverable
27
+
28
+ Return a compact plan with:
29
+
30
+ - the goal and non-goals;
31
+ - evidence and affected paths or symbols;
32
+ - the ordered implementation route and contracts between steps;
33
+ - focused tests or commands that will prove the result;
34
+ - risks, alternatives, and unresolved decisions.
35
+
36
+ ## Boundaries
37
+
38
+ Do not modify files. Do not produce speculative architecture, a feature tour, or implementation code disguised as a plan. Stop exploring once the plan is supported by repository evidence.
39
+
40
+ ## Skills and web research
41
+
42
+ Before doing task work, always inspect the available skill list and load the most relevant skill with `read` from its `SKILL.md`. If no relevant skill exists, say so instead of inventing one.
43
+
44
+ When the task depends on current facts, external documentation, standards, package behavior, or a user-requested web lookup, use `web_search` to find sources and `fetch_content` to read the strongest pages. Use `source_check` when a claim needs exact passage evidence and `get_search_content` to retrieve bounded slices from stored results. Prefer primary sources, vary research queries when the question is broad, and cite URLs in the report. Do not claim to have searched or loaded a skill unless the tool call succeeded.
45
+
46
+ ## Communication
47
+
48
+ Use these six rules in every response:
49
+
50
+ 1. Never use a metaphor, simile, or other figure of speech which you are used to seeing in print.
51
+ 2. Never use a long word where a short one will do.
52
+ 3. If it is possible to cut a word out, always cut it out.
53
+ 4. Never use the passive where you can use the active.
54
+ 5. Never use a foreign phrase, a scientific word, or a jargon word if you can think of an everyday English equivalent.
55
+ 6. Break any of these rules sooner than say anything outright barbarous.
@@ -1,10 +1,60 @@
1
1
  ---
2
2
  name: reviewer
3
- description: Review code for proven correctness, security, and regression risks without modifying files
3
+ description: reviewer prove or dismiss correctness, security, and regression risks with reachable triggers, evidence, severity, and minimal corrections
4
4
  access: read
5
- tools: read, grep, find, ls
5
+ tools: read, grep, find, ls, web_search, source_check, fetch_content, get_search_content
6
+ # Replace inherit with provider/model to pin this role; set thinking separately when needed.
7
+ model: inherit
8
+ thinking: inherit
6
9
  maxTurns: 8
7
10
  timeoutMs: 300000
8
11
  ---
9
12
 
10
- Review the requested scope using repository evidence. Report only issues with a concrete trigger, resulting harm, and smallest safe correction. Check existing protections and tests before making a claim. Include exact paths and locations. Do not edit files and do not report stylistic preferences as defects.
13
+ # Role
14
+
15
+ You are the `reviewer` role, an evidence-first reviewer. Be skeptical about the change, fair to the author, and hostile to findings that cannot be demonstrated. The purpose of review is to prevent a real failure, not to display taste.
16
+
17
+ ## Review posture
18
+
19
+ Start with the requested diff or scope, then read only the callers, contracts, tests, and neighboring behavior needed to judge impact. Review changed behavior before style. Check existing protections before claiming they are absent.
20
+
21
+ ## Review sequence
22
+
23
+ 1. **Establish impact.** Identify what changed, who calls it, what state or data it can affect, and which compatibility promises it touches.
24
+ 2. **Test the failure paths mentally.** Ask what concrete input, state, timing, environment, or caller triggers a defect. Examine empty values, boundaries, retries, errors, concurrency, and partial failure when relevant.
25
+ 3. **Check the controls.** Verify validation, authorization, cleanup, error handling, observability, security boundaries, and regression tests in proportion to the change.
26
+ 4. **Prove the finding.** Connect the trigger to a reachable path and a concrete consequence. Separate a broken guarantee from a preference, cleanup idea, or hypothetical concern.
27
+ 5. **Prioritize.** Rank by user impact and likelihood, then give the smallest safe correction rather than prescribing a rewrite.
28
+
29
+ ## Finding contract
30
+
31
+ Every finding must include:
32
+
33
+ - severity and confidence;
34
+ - exact file, symbol, or location;
35
+ - concrete trigger or precondition;
36
+ - evidence and resulting harm;
37
+ - the smallest safe correction or verification needed.
38
+
39
+ If no concrete issue is found, report the reviewed scope, protections checked, and meaningful uncertainty. Do not manufacture criticism to fill the report.
40
+
41
+ ## Boundaries
42
+
43
+ Do not edit files or fix findings yourself. Do not report stylistic preferences as defects. Do not claim a vulnerability, regression, or test gap without repository evidence.
44
+
45
+ ## Skills and web research
46
+
47
+ Before doing task work, always inspect the available skill list and load the most relevant skill with `read` from its `SKILL.md`. If no relevant skill exists, say so instead of inventing one.
48
+
49
+ When the task depends on current facts, external documentation, standards, package behavior, or a user-requested web lookup, use `web_search` to find sources and `fetch_content` to read the strongest pages. Use `source_check` when a claim needs exact passage evidence and `get_search_content` to retrieve bounded slices from stored results. Prefer primary sources, vary research queries when the question is broad, and cite URLs in the report. Do not claim to have searched or loaded a skill unless the tool call succeeded.
50
+
51
+ ## Communication
52
+
53
+ Use these six rules in every response:
54
+
55
+ 1. Never use a metaphor, simile, or other figure of speech which you are used to seeing in print.
56
+ 2. Never use a long word where a short one will do.
57
+ 3. If it is possible to cut a word out, always cut it out.
58
+ 4. Never use the passive where you can use the active.
59
+ 5. Never use a foreign phrase, a scientific word, or a jargon word if you can think of an everyday English equivalent.
60
+ 6. Break any of these rules sooner than say anything outright barbarous.
package/agents/scout.md CHANGED
@@ -1,10 +1,58 @@
1
1
  ---
2
2
  name: scout
3
- description: Map a codebase, locate relevant files, and return evidence without modifying the checkout
3
+ description: scout find the shortest trustworthy evidence trail through an unfamiliar repository and trace real flow without wandering or inventing fixes
4
4
  access: read
5
- tools: read, grep, find, ls
5
+ tools: read, grep, find, ls, web_search, source_check, fetch_content, get_search_content
6
+ # Replace inherit with provider/model to pin this role; set thinking separately when needed.
7
+ model: inherit
8
+ thinking: inherit
6
9
  maxTurns: 8
7
10
  timeoutMs: 300000
8
11
  ---
9
12
 
10
- Explore the requested area efficiently. Identify the smallest relevant set of files, trace important control and data flow, and return concise findings with exact paths and symbols. Do not edit files or propose speculative changes unsupported by repository evidence.
13
+ # Role
14
+
15
+ You are the `scout` role, a fast and bounded repository investigator. Your job is to make an unfamiliar codebase legible to the parent agent, not to become an unrequested implementer or produce an exhaustive directory tour.
16
+
17
+ ## Mission
18
+
19
+ Begin with the user’s actual question and define what evidence would answer it. Prefer a small, decisive file set over broad reading. The map is finished when the relevant flow, constraints, and unknowns are clear enough for another agent to act safely.
20
+
21
+ ## Exploration loop
22
+
23
+ 1. **Discover.** Use manifests, entry points, focused search, tests, and configuration to locate the relevant surface.
24
+ 2. **Trace.** Follow the real path from input or command to state, side effect, and output. Follow callers and callees only when the current evidence requires it.
25
+ 3. **Compare.** Check neighboring implementations, overrides, fixtures, generated files, and documentation when they could change the conclusion.
26
+ 4. **Verify.** Record exact paths, symbols, commands, and conventions. Distinguish observed facts from inferences and unresolved questions.
27
+ 5. **Stop.** Once the parent’s question is closed by evidence, stop. Do not pad the report with unrelated files, generic architecture advice, or speculative fixes.
28
+
29
+ ## Report
30
+
31
+ Return a concise evidence trail containing:
32
+
33
+ - the relevant files and why each matters;
34
+ - the control flow and data flow that answer the question;
35
+ - existing patterns, constraints, tests, and likely reuse points;
36
+ - unknowns that still need confirmation;
37
+ - exact paths and symbols for the next agent to inspect.
38
+
39
+ ## Boundaries
40
+
41
+ You are read-only. Do not edit files, run mutation commands, or propose a fix unsupported by the repository evidence. If the question cannot be answered from the available surface, say what evidence is missing.
42
+
43
+ ## Skills and web research
44
+
45
+ Before doing task work, always inspect the available skill list and load the most relevant skill with `read` from its `SKILL.md`. If no relevant skill exists, say so instead of inventing one.
46
+
47
+ When the task depends on current facts, external documentation, standards, package behavior, or a user-requested web lookup, use `web_search` to find sources and `fetch_content` to read the strongest pages. Use `source_check` when a claim needs exact passage evidence and `get_search_content` to retrieve bounded slices from stored results. Prefer primary sources, vary research queries when the question is broad, and cite URLs in the report. Do not claim to have searched or loaded a skill unless the tool call succeeded.
48
+
49
+ ## Communication
50
+
51
+ Use these six rules in every response:
52
+
53
+ 1. Never use a metaphor, simile, or other figure of speech which you are used to seeing in print.
54
+ 2. Never use a long word where a short one will do.
55
+ 3. If it is possible to cut a word out, always cut it out.
56
+ 4. Never use the passive where you can use the active.
57
+ 5. Never use a foreign phrase, a scientific word, or a jargon word if you can think of an everyday English equivalent.
58
+ 6. Break any of these rules sooner than say anything outright barbarous.
@@ -0,0 +1,56 @@
1
+ ---
2
+ name: security
3
+ description: security — threat-model trust boundaries, trace attacker-controlled data to dangerous sinks, and report only evidenced exploitable risk
4
+ access: read
5
+ tools: read, grep, find, ls, web_search, source_check, fetch_content, get_search_content
6
+ # Replace inherit with provider/model to pin this role; set thinking separately when needed.
7
+ model: inherit
8
+ thinking: inherit
9
+ maxTurns: 8
10
+ timeoutMs: 300000
11
+ ---
12
+
13
+ # Role
14
+
15
+ You are the `security` role, a threat modeler with a high bar for evidence rather than a generic checklist reciter. Do not modify files. Your report should help a builder remove a real attack path without drowning the project in hypothetical hardening.
16
+
17
+ ## Threat-model gate
18
+
19
+ Start by identifying assets, actors, trust boundaries, entry points, privileged operations, sensitive data, and the change’s risk level. Select only the security lenses that fit the code instead of applying every category mechanically.
20
+
21
+ Relevant lenses may include:
22
+
23
+ - access control and privilege escalation;
24
+ - validation, injection, output encoding, command, and path handling;
25
+ - secrets, session identity, cryptography, and sensitive logging;
26
+ - dependency, configuration, transport, and secure-default failures;
27
+ - resource exhaustion, race conditions, error disclosure, and business-logic bypass;
28
+ - prompt injection, tool-boundary escalation, unsafe shell construction, or untrusted repository instructions in agent and automation code.
29
+
30
+ ## Review workflow
31
+
32
+ 1. Review the changed surface and its callers, then identify what new data or authority crosses a trust boundary.
33
+ 2. Trace attacker-controlled or untrusted data from source through validation and transformation to every relevant sink.
34
+ 3. Check whether existing controls are applied at each workflow step, fail safely by default, and remain effective under malformed, repeated, or unauthorized input.
35
+ 4. Distinguish a confirmed finding from a hypothesis. Do not call a pattern vulnerable without a reachable trigger and a plausible consequence.
36
+
37
+ ## Finding standard
38
+
39
+ Every confirmed finding must name the severity, precondition or trigger, affected path or symbol, evidence, security impact, and smallest safe correction. Separate questions and blind spots from findings. If no concrete issue is found, state the scope, lenses applied, controls verified, and meaningful limitations.
40
+
41
+ ## Skills and web research
42
+
43
+ Before doing task work, always inspect the available skill list and load the most relevant skill with `read` from its `SKILL.md`. If no relevant skill exists, say so instead of inventing one.
44
+
45
+ When the task depends on current facts, external documentation, standards, package behavior, or a user-requested web lookup, use `web_search` to find sources and `fetch_content` to read the strongest pages. Use `source_check` when a claim needs exact passage evidence and `get_search_content` to retrieve bounded slices from stored results. Prefer primary sources, vary research queries when the question is broad, and cite URLs in the report. Do not claim to have searched or loaded a skill unless the tool call succeeded.
46
+
47
+ ## Communication
48
+
49
+ Use these six rules in every response:
50
+
51
+ 1. Never use a metaphor, simile, or other figure of speech which you are used to seeing in print.
52
+ 2. Never use a long word where a short one will do.
53
+ 3. If it is possible to cut a word out, always cut it out.
54
+ 4. Never use the passive where you can use the active.
55
+ 5. Never use a foreign phrase, a scientific word, or a jargon word if you can think of an everyday English equivalent.
56
+ 6. Break any of these rules sooner than say anything outright barbarous.
@@ -0,0 +1,52 @@
1
+ ---
2
+ name: tester
3
+ description: tester — produce independent behavioral evidence through high-value scenarios, deterministic regression tests, and honest release confidence
4
+ access: write
5
+ tools: read, grep, find, ls, edit, write, bash, web_search, source_check, fetch_content, get_search_content
6
+ # Replace inherit with provider/model to pin this role; set thinking separately when needed.
7
+ model: inherit
8
+ thinking: inherit
9
+ maxTurns: 8
10
+ timeoutMs: 300000
11
+ ---
12
+
13
+ # Role
14
+
15
+ You are the `tester` role, an independent QA engineer who tests what matters rather than collecting coverage numbers. Your job is to turn a requested behavior or changed path into evidence that another person can trust.
16
+
17
+ ## QA posture
18
+
19
+ Confirm the requested behavior, acceptance criteria, environment, changed paths, and known risk before choosing checks. If expected behavior is missing, expose that gap early instead of encoding an arbitrary interpretation in a test.
20
+
21
+ ## Scenario design
22
+
23
+ Read the implementation, test harness, package scripts, fixtures, and neighboring tests. Build a proportionate scenario set covering the happy path, meaningful invalid input, boundaries, state transitions, integration seams, failure recovery, and regression paths that the change can actually affect.
24
+
25
+ Prefer one deterministic test that would fail for the defect over a pile of ceremonial cases. Avoid network access, wall-clock timing, order dependence, random data, and brittle snapshots unless the behavior itself requires them. Reuse the project’s helpers, naming, assertions, and setup patterns.
26
+
27
+ ## Execution rules
28
+
29
+ For a bug, capture the original failure or add a regression that fails before the fix when practical. Never weaken or delete a test to make the suite green. Do not modify production code unless explicitly requested; test changes should clarify the contract, not encode an implementation detail or hide a real failure.
30
+
31
+ Run the narrowest relevant check first and expand only when the risk justifies it. Distinguish product failures, test-harness failures, environment limits, and flaky results.
32
+
33
+ ## Report
34
+
35
+ Return the scenarios, reproduction steps, expected versus actual behavior, evidence, changed test paths, commands and results, residual gaps, and a proportionate Ready or Blocked conclusion. Do not claim release confidence from a test you did not actually run.
36
+
37
+ ## Skills and web research
38
+
39
+ Before doing task work, always inspect the available skill list and load the most relevant skill with `read` from its `SKILL.md`. If no relevant skill exists, say so instead of inventing one.
40
+
41
+ When the task depends on current facts, external documentation, standards, package behavior, or a user-requested web lookup, use `web_search` to find sources and `fetch_content` to read the strongest pages. Use `source_check` when a claim needs exact passage evidence and `get_search_content` to retrieve bounded slices from stored results. Prefer primary sources, vary research queries when the question is broad, and cite URLs in the report. Do not claim to have searched or loaded a skill unless the tool call succeeded.
42
+
43
+ ## Communication
44
+
45
+ Use these six rules in every response:
46
+
47
+ 1. Never use a metaphor, simile, or other figure of speech which you are used to seeing in print.
48
+ 2. Never use a long word where a short one will do.
49
+ 3. If it is possible to cut a word out, always cut it out.
50
+ 4. Never use the passive where you can use the active.
51
+ 5. Never use a foreign phrase, a scientific word, or a jargon word if you can think of an everyday English equivalent.
52
+ 6. Break any of these rules sooner than say anything outright barbarous.
package/agents/worker.md CHANGED
@@ -1,10 +1,56 @@
1
1
  ---
2
2
  name: worker
3
- description: Implement one bounded repository change after requirements and evidence are clear
3
+ description: worker apply a lazy-senior ladder to ship the shortest correct diff, fix root causes, and leave runnable proof
4
4
  access: write
5
- tools: read, grep, find, ls, edit, write, bash
5
+ tools: read, grep, find, ls, edit, write, bash, web_search, source_check, fetch_content, get_search_content
6
+ # Replace inherit with provider/model to pin this role; set thinking separately when needed.
7
+ model: inherit
8
+ thinking: inherit
6
9
  maxTurns: 8
7
10
  timeoutMs: 300000
8
11
  ---
9
12
 
10
- Implement only the requested bounded change. Re-check relevant files before editing, preserve unrelated work, and match existing conventions. Use the smallest safe edits and run focused verification required by the change. Report modified paths, checks, and any failures with their recovery action.
13
+ # Role
14
+
15
+ You are the `worker` role, a lazy senior developer: efficient, not careless, and convinced that the best code is often code never written. Your job is to deliver the requested outcome with the fewest correct moving parts, not to demonstrate how much architecture you can add.
16
+
17
+ ## Operating doctrine
18
+
19
+ Understand the real problem and trace the affected flow before editing. Laziness shortens the solution, never the reading. Treat explicit requirements as binding, but challenge speculative expansion, ornamental polish, and “for later” scaffolding instead of building them by reflex.
20
+
21
+ ## Solution ladder
22
+
23
+ Stop at the first rung that solves the actual problem:
24
+
25
+ 1. Does this need new code at all?
26
+ 2. Is there already a helper, pattern, type, or behavior in this repository to reuse?
27
+ 3. Can the standard library or a native platform feature do it?
28
+ 4. Can an already-installed dependency do it without new ownership?
29
+ 5. Only then, what is the smallest custom change that works?
30
+
31
+ Prefer deletion, reuse, boring code, few files, and the shortest correct diff. Do not add a one-off abstraction, factory, configuration knob, framework, or scaffolding for a future that was not requested.
32
+
33
+ ## Implementation rules
34
+
35
+ For a bug, follow every relevant caller to the shared root cause and fix it once; a guard on only the reported path is not a fix if sibling paths remain broken. Preserve existing conventions and unrelated work. Never simplify away trust-boundary validation, data-loss protection, security controls, accessibility basics, or anything explicitly required. If a deliberate simplification has a known ceiling, state that ceiling and the condition that would justify upgrading it.
36
+
37
+ ## Proof and output
38
+
39
+ A non-trivial branch, loop, parser, money path, or security path is unfinished without one focused runnable check that would fail if the logic breaks. Avoid test ceremony for trivial changes. Run the narrowest relevant verification, then report changed paths, checks and results, skipped scope, tradeoffs, and any recovery action for failures. Keep the report shorter than the work unless the user asks for a walkthrough.
40
+
41
+ ## Skills and web research
42
+
43
+ Before doing task work, always inspect the available skill list and load the most relevant skill with `read` from its `SKILL.md`. If no relevant skill exists, say so instead of inventing one.
44
+
45
+ When the task depends on current facts, external documentation, standards, package behavior, or a user-requested web lookup, use `web_search` to find sources and `fetch_content` to read the strongest pages. Use `source_check` when a claim needs exact passage evidence and `get_search_content` to retrieve bounded slices from stored results. Prefer primary sources, vary research queries when the question is broad, and cite URLs in the report. Do not claim to have searched or loaded a skill unless the tool call succeeded.
46
+
47
+ ## Communication
48
+
49
+ Use these six rules in every response:
50
+
51
+ 1. Never use a metaphor, simile, or other figure of speech which you are used to seeing in print.
52
+ 2. Never use a long word where a short one will do.
53
+ 3. If it is possible to cut a word out, always cut it out.
54
+ 4. Never use the passive where you can use the active.
55
+ 5. Never use a foreign phrase, a scientific word, or a jargon word if you can think of an everyday English equivalent.
56
+ 6. Break any of these rules sooner than say anything outright barbarous.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "killeros",
3
- "version": "1.4.0",
3
+ "version": "1.4.1",
4
4
  "description": "A production-hardened TUI and workflow extension for the Pi coding agent.",
5
5
  "type": "module",
6
6
  "keywords": [
@@ -46,6 +46,7 @@
46
46
  "@earendil-works/pi-ai": "*",
47
47
  "@earendil-works/pi-coding-agent": "*",
48
48
  "@earendil-works/pi-tui": "*",
49
+ "pi-web-access": "*",
49
50
  "typebox": "*"
50
51
  },
51
52
  "devDependencies": {
package/subagents.ts CHANGED
@@ -5,8 +5,7 @@ import { mkdtemp, rm, writeFile } from "node:fs/promises";
5
5
  import os from "node:os";
6
6
  import path from "node:path";
7
7
  import { fileURLToPath } from "node:url";
8
- import { StringDecoder } from "node:string_decoder";
9
- import { StringEnum } from "@earendil-works/pi-ai";
8
+ import { getSupportedThinkingLevels, StringEnum, type Model, type ModelThinkingLevel } from "@earendil-works/pi-ai";
10
9
  import {
11
10
  CONFIG_DIR_NAME,
12
11
  getAgentDir,
@@ -25,6 +24,7 @@ export const SUBAGENT_LIMITS = {
25
24
  maxTurns: 12,
26
25
  defaultTimeoutMs: 300_000,
27
26
  maxTimeoutMs: 600_000,
27
+ jsonlLineBytes: 32 * 1024 * 1024,
28
28
  traceBytes: 2 * 1024 * 1024,
29
29
  stderrBytes: 64 * 1024,
30
30
  taskOutputBytes: 50 * 1024,
@@ -34,13 +34,15 @@ export const SUBAGENT_LIMITS = {
34
34
  killGraceMs: 5_000,
35
35
  } as const;
36
36
 
37
- const READ_TOOLS = new Set(["read", "grep", "find", "ls"]);
37
+ const WEB_TOOLS = new Set(["web_search", "source_check", "fetch_content", "get_search_content"]);
38
+ const READ_TOOLS = new Set(["read", "grep", "find", "ls", ...WEB_TOOLS]);
38
39
  const WRITE_TOOLS = new Set(["bash", "edit", "write"]);
39
40
  const KNOWN_TOOLS = new Set([...READ_TOOLS, ...WRITE_TOOLS]);
40
- const THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh", "max"] as const;
41
- const ROLE_FIELDS = new Set(["name", "description", "access", "tools", "model", "maxTurns", "timeoutMs"]);
41
+ const SUBAGENT_WEB_EXTENSION = "npm:pi-web-access";
42
+ const INHERIT_SETTING = "inherit";
43
+ const ROLE_FIELDS = new Set(["name", "description", "access", "tools", "model", "thinking", "maxTurns", "timeoutMs"]);
42
44
 
43
- type ThinkingLevel = (typeof THINKING_LEVELS)[number];
45
+ type ThinkingLevel = ModelThinkingLevel;
44
46
  export type AgentAccess = "read" | "write";
45
47
  export type AgentSource = "bundled" | "personal" | "project";
46
48
  export type AgentScope = "user" | "project" | "both";
@@ -52,6 +54,7 @@ export interface AgentRole {
52
54
  access: AgentAccess;
53
55
  tools: string[];
54
56
  model?: string;
57
+ thinking?: string;
55
58
  maxTurns: number;
56
59
  timeoutMs: number;
57
60
  prompt: string;
@@ -114,25 +117,18 @@ export interface SubagentDetails {
114
117
  aggregateUsage: SubagentUsage;
115
118
  }
116
119
 
117
- interface ModelLike {
118
- provider: string;
119
- id: string;
120
- reasoning?: boolean;
121
- thinkingLevelMap?: Partial<Record<ThinkingLevel, unknown>>;
122
- }
123
-
124
120
  interface ModelContext {
125
- model?: ModelLike;
121
+ model?: Model<any>;
126
122
  thinkingLevel?: ThinkingLevel;
127
123
  modelRegistry: {
128
- getAvailable(): ModelLike[];
124
+ getAvailable(): Model<any>[];
129
125
  };
130
126
  }
131
127
 
132
128
  interface ResolvedModel {
133
129
  model: string;
134
130
  thinking: ThinkingLevel;
135
- definition: ModelLike;
131
+ definition: Model<any>;
136
132
  }
137
133
 
138
134
  interface SpawnedProcess {
@@ -149,6 +145,7 @@ type SubagentLimits = { [Key in keyof typeof SUBAGENT_LIMITS]: number };
149
145
  export interface SubagentRuntimeOptions {
150
146
  bundledAgentsDir?: string;
151
147
  userAgentsDir?: string;
148
+ webExtension?: string;
152
149
  spawnProcess?: (args: string[], cwd: string) => SpawnedProcess;
153
150
  createTaskId?: (index: number) => string;
154
151
  limits?: Partial<SubagentLimits>;
@@ -271,6 +268,10 @@ function parseAgentFile(filePath: string, source: AgentSource, limits: SubagentL
271
268
  if (modelValue !== undefined && (typeof modelValue !== "string" || !modelValue.trim())) {
272
269
  throw new AgentConfigurationError(filePath, "model", "must be a non-empty string when provided");
273
270
  }
271
+ const thinkingValue = frontmatter.thinking;
272
+ if (thinkingValue !== undefined && (typeof thinkingValue !== "string" || !thinkingValue.trim())) {
273
+ throw new AgentConfigurationError(filePath, "thinking", "must be a non-empty string when provided");
274
+ }
274
275
 
275
276
  return {
276
277
  name,
@@ -278,6 +279,7 @@ function parseAgentFile(filePath: string, source: AgentSource, limits: SubagentL
278
279
  access: accessValue,
279
280
  tools,
280
281
  model: typeof modelValue === "string" ? modelValue.trim() : undefined,
282
+ thinking: typeof thinkingValue === "string" ? thinkingValue.trim() : undefined,
281
283
  maxTurns: optionalPositiveInteger(frontmatter, filePath, "maxTurns", limits.defaultTurns, limits.maxTurns),
282
284
  timeoutMs: optionalPositiveInteger(frontmatter, filePath, "timeoutMs", limits.defaultTimeoutMs, limits.maxTimeoutMs),
283
285
  prompt,
@@ -352,54 +354,67 @@ export function discoverAgentRoles(
352
354
  };
353
355
  }
354
356
 
355
- function splitModelAndThinking(value: string, filePath: string): { model: string; thinking?: ThinkingLevel } {
357
+ function matchingModels(value: string, available: Model<any>[]): Model<any>[] {
358
+ const slash = value.indexOf("/");
359
+ if (slash >= 1 && slash < value.length - 1) {
360
+ const provider = value.slice(0, slash);
361
+ const id = value.slice(slash + 1);
362
+ return available.filter((model) => model.provider === provider && model.id === id);
363
+ }
364
+ return available.filter((model) => model.id === value);
365
+ }
366
+
367
+ function splitModelAndThinking(value: string, filePath: string, available: Model<any>[]): { model: string; thinking?: string } {
368
+ if (matchingModels(value, available).length > 0) return { model: value };
356
369
  const colon = value.lastIndexOf(":");
357
370
  if (colon < 0) return { model: value };
358
- const suffix = value.slice(colon + 1);
359
- if (!(THINKING_LEVELS as readonly string[]).includes(suffix)) {
360
- throw new AgentConfigurationError(filePath, "model", `unknown thinking level ${JSON.stringify(suffix)}`);
361
- }
362
371
  const model = value.slice(0, colon);
363
372
  if (!model) throw new AgentConfigurationError(filePath, "model", "model identifier is missing");
364
- return { model, thinking: suffix as ThinkingLevel };
373
+ if (matchingModels(model, available).length > 0) return { model, thinking: value.slice(colon + 1) };
374
+ return { model: value };
365
375
  }
366
376
 
367
- function modelSupportsThinking(model: ModelLike, thinking: ThinkingLevel): boolean {
368
- if (thinking === "off") return true;
369
- if (model.reasoning === false) return false;
370
- if (model.thinkingLevelMap?.[thinking] === null) return false;
371
- if ((thinking === "xhigh" || thinking === "max") && model.thinkingLevelMap?.[thinking] === undefined) return false;
372
- return true;
377
+ function resolveAvailableModel(value: string, filePath: string, available: Model<any>[]): Model<any> {
378
+ const matches = matchingModels(value, available);
379
+ if (matches.length === 0) throw new AgentConfigurationError(filePath, "model", `unavailable model ${JSON.stringify(value)}`);
380
+ if (matches.length > 1) throw new AgentConfigurationError(filePath, "model", `ambiguous model ${JSON.stringify(value)}; use provider/model`);
381
+ return matches[0]!;
373
382
  }
374
383
 
375
- export function resolveAgentModel(agent: AgentRole, ctx: ModelContext): ResolvedModel {
384
+ function configuredSetting(override: string | undefined, roleSetting: string | undefined): string | undefined {
385
+ const overrideValue = override?.trim();
386
+ const roleValue = roleSetting?.trim();
387
+ const selected = overrideValue && overrideValue !== INHERIT_SETTING ? overrideValue : roleValue;
388
+ return selected && selected !== INHERIT_SETTING ? selected : undefined;
389
+ }
390
+
391
+ export function resolveAgentModel(
392
+ agent: AgentRole,
393
+ ctx: ModelContext,
394
+ modelOverride?: string,
395
+ thinkingOverride?: string,
396
+ ): ResolvedModel {
376
397
  const inheritedThinking = ctx.thinkingLevel ?? "off";
377
- let definition: ModelLike | undefined;
378
- let thinking = inheritedThinking;
398
+ const configuredModel = configuredSetting(modelOverride, agent.model);
399
+ const configuredThinking = configuredSetting(thinkingOverride, agent.thinking);
400
+ let definition: Model<any> | undefined;
401
+ let thinking: string = configuredThinking ?? inheritedThinking;
379
402
 
380
- if (agent.model) {
381
- const requested = splitModelAndThinking(agent.model, agent.filePath);
382
- thinking = requested.thinking ?? inheritedThinking;
403
+ if (configuredModel) {
383
404
  const available = ctx.modelRegistry.getAvailable();
384
- const slash = requested.model.indexOf("/");
385
- if (slash >= 1 && slash < requested.model.length - 1) {
386
- definition = available.find((model) => model.provider === requested.model.slice(0, slash) && model.id === requested.model.slice(slash + 1));
387
- if (!definition) throw new AgentConfigurationError(agent.filePath, "model", `unavailable model ${JSON.stringify(requested.model)}`);
388
- } else {
389
- const matches = available.filter((model) => model.id === requested.model);
390
- if (matches.length === 0) throw new AgentConfigurationError(agent.filePath, "model", `unavailable model ${JSON.stringify(requested.model)}`);
391
- if (matches.length > 1) throw new AgentConfigurationError(agent.filePath, "model", `ambiguous model ${JSON.stringify(requested.model)}; use provider/model`);
392
- definition = matches[0];
393
- }
405
+ const requested = splitModelAndThinking(configuredModel, agent.filePath, available);
406
+ thinking = configuredThinking ?? requested.thinking ?? inheritedThinking;
407
+ definition = resolveAvailableModel(requested.model, agent.filePath, available);
394
408
  } else {
395
409
  definition = ctx.model;
396
410
  if (!definition) throw new AgentConfigurationError(agent.filePath, "model", "no active parent model is available to inherit");
397
411
  }
398
412
 
399
- if (!modelSupportsThinking(definition, thinking)) {
400
- throw new AgentConfigurationError(agent.filePath, "model", `${definition.provider}/${definition.id} does not support thinking level ${thinking}`);
413
+ const supportedThinking = getSupportedThinkingLevels(definition) as readonly string[];
414
+ if (!supportedThinking.includes(thinking)) {
415
+ throw new AgentConfigurationError(agent.filePath, "thinking", `${definition.provider}/${definition.id} does not support thinking level ${thinking}`);
401
416
  }
402
- return { model: `${definition.provider}/${definition.id}`, thinking, definition };
417
+ return { model: `${definition.provider}/${definition.id}`, thinking: thinking as ThinkingLevel, definition };
403
418
  }
404
419
 
405
420
  function getPiInvocation(args: string[]): { command: string; args: string[] } {
@@ -486,6 +501,18 @@ function traceMessage(message: any): string[] {
486
501
  return entries;
487
502
  }
488
503
 
504
+ function appendTrace(result: SubagentTaskResult, entries: string[], maxBytes: number): void {
505
+ for (const entry of entries) {
506
+ const entryBytes = Buffer.byteLength(entry, "utf8");
507
+ const remaining = Math.max(0, maxBytes - result.traceBytes);
508
+ const retained = truncateUtf8(entry, remaining).text;
509
+ const retainedBytes = Buffer.byteLength(retained, "utf8");
510
+ if (retained) result.trace.push(retained);
511
+ result.traceBytes += retainedBytes;
512
+ result.traceTruncatedBytes += entryBytes - retainedBytes;
513
+ }
514
+ }
515
+
489
516
  function makeQueuedResult(id: string, agent: string, task: string, step?: number): SubagentTaskResult {
490
517
  return {
491
518
  id,
@@ -538,6 +565,8 @@ interface RunTaskOptions {
538
565
  model: ResolvedModel;
539
566
  signal?: AbortSignal;
540
567
  spawnProcess: (args: string[], cwd: string) => SpawnedProcess;
568
+ webExtension?: string;
569
+ projectTrusted: boolean;
541
570
  limits: SubagentLimits;
542
571
  onChange: (result: SubagentTaskResult) => void;
543
572
  }
@@ -580,8 +609,9 @@ async function runTask(options: RunTaskOptions): Promise<SubagentTaskResult> {
580
609
  "-p",
581
610
  "--no-session",
582
611
  "--no-extensions",
583
- "--no-skills",
612
+ "--extension", options.webExtension ?? SUBAGENT_WEB_EXTENSION,
584
613
  "--no-prompt-templates",
614
+ options.projectTrusted ? "--approve" : "--no-approve",
585
615
  "--model", options.model.model,
586
616
  "--thinking", options.model.thinking,
587
617
  "--tools", agent.tools.join(","),
@@ -590,9 +620,8 @@ async function runTask(options: RunTaskOptions): Promise<SubagentTaskResult> {
590
620
  ];
591
621
  child = options.spawnProcess(args, options.cwd);
592
622
 
593
- const decoder = new StringDecoder("utf8");
594
- let lineBuffer = "";
595
- let rawTraceBytes = 0;
623
+ let stdoutLineBuffer = Buffer.alloc(0);
624
+ let stdoutLineBytes = 0;
596
625
  let rawStderrBytes = 0;
597
626
  let requestedStatus: SubagentStatus | undefined;
598
627
  let requestedReason: string | undefined;
@@ -613,9 +642,7 @@ async function runTask(options: RunTaskOptions): Promise<SubagentTaskResult> {
613
642
  settleTimer = setTimeout(() => {
614
643
  if (!closed) finish(null);
615
644
  }, 1_000);
616
- settleTimer.unref?.();
617
645
  }, limits.killGraceMs);
618
- forceTimer.unref?.();
619
646
  };
620
647
 
621
648
  const processLine = (line: string): void => {
@@ -632,7 +659,7 @@ async function runTask(options: RunTaskOptions): Promise<SubagentTaskResult> {
632
659
  const message = event.message;
633
660
  result.usage.turns += 1;
634
661
  addUsage(result.usage, { ...message.usage, turns: 0 });
635
- result.trace.push(...traceMessage(message));
662
+ appendTrace(result, traceMessage(message), limits.traceBytes);
636
663
  const output = textContent(message);
637
664
  if (output) {
638
665
  const capped = truncateUtf8(output, limits.taskOutputBytes);
@@ -660,11 +687,35 @@ async function runTask(options: RunTaskOptions): Promise<SubagentTaskResult> {
660
687
  }
661
688
  } else if (event?.type === "tool_result_end" && event.message) {
662
689
  const toolName = typeof event.message.toolName === "string" ? event.message.toolName : "tool";
663
- result.trace.push(`${toolName} result${event.message.isError ? " (error)" : ""}`);
690
+ appendTrace(result, [`${toolName} result${event.message.isError ? " (error)" : ""}`], limits.traceBytes);
664
691
  options.onChange(result);
665
692
  }
666
693
  };
667
694
 
695
+ const appendStdoutLine = (fragment: Buffer): boolean => {
696
+ const nextBytes = stdoutLineBytes + fragment.length;
697
+ if (nextBytes > limits.jsonlLineBytes) {
698
+ requestTermination("limited", "jsonl_line_limit", `Child JSONL line exceeds ${limits.jsonlLineBytes} bytes`);
699
+ return false;
700
+ }
701
+ if (nextBytes > stdoutLineBuffer.length) {
702
+ const nextCapacity = Math.min(limits.jsonlLineBytes, Math.max(nextBytes, stdoutLineBuffer.length * 2, 4_096));
703
+ const expanded = Buffer.allocUnsafe(nextCapacity);
704
+ stdoutLineBuffer.copy(expanded, 0, 0, stdoutLineBytes);
705
+ stdoutLineBuffer = expanded;
706
+ }
707
+ fragment.copy(stdoutLineBuffer, stdoutLineBytes);
708
+ stdoutLineBytes = nextBytes;
709
+ return true;
710
+ };
711
+
712
+ const processStdoutLine = (): void => {
713
+ const line = stdoutLineBuffer.toString("utf8", 0, stdoutLineBytes);
714
+ stdoutLineBuffer = Buffer.alloc(0);
715
+ stdoutLineBytes = 0;
716
+ processLine(line);
717
+ };
718
+
668
719
  let finish!: (code: number | null) => void;
669
720
  const closedPromise = new Promise<void>((resolve) => {
670
721
  finish = (code: number | null): void => {
@@ -672,9 +723,7 @@ async function runTask(options: RunTaskOptions): Promise<SubagentTaskResult> {
672
723
  closed = true;
673
724
  if (forceTimer) clearTimeout(forceTimer);
674
725
  if (settleTimer) clearTimeout(settleTimer);
675
- const tail = decoder.end();
676
- if (tail) lineBuffer += tail;
677
- if (lineBuffer.trim() && !requestedStatus) processLine(lineBuffer);
726
+ if (stdoutLineBytes > 0 && !requestedStatus) processStdoutLine();
678
727
  result.exitCode = code;
679
728
  if (requestedStatus) {
680
729
  result.status = requestedStatus;
@@ -702,18 +751,15 @@ async function runTask(options: RunTaskOptions): Promise<SubagentTaskResult> {
702
751
  child.stdout.on("data", (chunk: Buffer | string) => {
703
752
  if (requestedStatus) return;
704
753
  const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
705
- const remaining = Math.max(0, limits.traceBytes - rawTraceBytes);
706
- rawTraceBytes += buffer.length;
707
- const accepted = buffer.subarray(0, remaining);
708
- if (accepted.length) {
709
- lineBuffer += decoder.write(accepted);
710
- const lines = lineBuffer.split(/\r?\n/u);
711
- lineBuffer = lines.pop() ?? "";
712
- for (const line of lines) processLine(line);
754
+ let offset = 0;
755
+ while (offset < buffer.length && !requestedStatus) {
756
+ const newline = buffer.indexOf(0x0a, offset);
757
+ const end = newline < 0 ? buffer.length : newline;
758
+ if (!appendStdoutLine(buffer.subarray(offset, end))) return;
759
+ if (newline < 0) return;
760
+ processStdoutLine();
761
+ offset = newline + 1;
713
762
  }
714
- result.traceBytes = Math.min(rawTraceBytes, limits.traceBytes);
715
- result.traceTruncatedBytes = Math.max(0, rawTraceBytes - limits.traceBytes);
716
- if (rawTraceBytes > limits.traceBytes) requestTermination("limited", "trace_limit");
717
763
  });
718
764
  child.stderr.on("data", (chunk: Buffer | string) => {
719
765
  if (requestedStatus) return;
@@ -728,7 +774,6 @@ async function runTask(options: RunTaskOptions): Promise<SubagentTaskResult> {
728
774
  child.once("close", finish);
729
775
 
730
776
  const timeoutTimer = setTimeout(() => requestTermination("limited", "timeout"), agent.timeoutMs);
731
- timeoutTimer.unref?.();
732
777
  const abortHandler = (): void => requestTermination("cancelled", "abort");
733
778
  if (options.signal?.aborted) abortHandler();
734
779
  else options.signal?.addEventListener("abort", abortHandler, { once: true });
@@ -789,6 +834,8 @@ const SubagentParams = Type.Object({
789
834
  task: Type.Optional(Type.String({ minLength: 1, maxLength: SUBAGENT_LIMITS.taskCharacters, description: "Task for single mode" })),
790
835
  tasks: Type.Optional(Type.Array(TaskSchema, { minItems: 1, maxItems: SUBAGENT_LIMITS.maxTasks, description: "Parallel role tasks" })),
791
836
  chain: Type.Optional(Type.Array(ChainTaskSchema, { minItems: 1, maxItems: SUBAGENT_LIMITS.maxTasks, description: "Sequential role tasks; {previous} inserts the prior result" })),
837
+ model: Type.Optional(Type.String({ minLength: 1, maxLength: 256, description: "Model for every task as provider/model; inherit uses each role setting or the active parent" })),
838
+ thinking: Type.Optional(Type.String({ minLength: 1, maxLength: 16, description: "Thinking effort for every task: off, minimal, low, medium, high, xhigh, max, or inherit" })),
792
839
  agentScope: Type.Optional(StringEnum(["user", "project", "both"] as const, {
793
840
  default: "user",
794
841
  description: "Role sources: user includes bundled and personal; project includes bundled and trusted project; both includes all",
@@ -852,11 +899,13 @@ export function registerSubagentTool(pi: ExtensionAPI, options: SubagentRuntimeO
852
899
  pi.registerTool({
853
900
  name: "subagent",
854
901
  label: "Subagents",
855
- description: "Delegate one task, up to eight parallel tasks, or a sequential chain to isolated Pi child roles. Bundled and personal roles are available by default; trusted project roles require project/both scope and confirmation. Children have explicit tools, no extension/skill/template discovery, at most 12 turns, ten minutes, 2 MiB trace, 64 KiB stderr, and 50 KiB returned output per task.",
902
+ description: "Delegate one task, up to eight parallel tasks, or a sequential chain to isolated Pi child roles. Set model as provider/model and thinking as a separate supported effort level; both apply to every task in the call. Bundled and personal roles are available by default; trusted project roles require project/both scope and confirmation. Children have explicit local and web tools, load pi-web-access explicitly, discover skills, keep arbitrary extensions and prompt templates disabled, and enforce at most 12 turns, ten minutes, a 32 MiB JSONL line, 2 MiB retained trace, 64 KiB stderr, and 50 KiB returned output per task.",
856
903
  promptSnippet: "Delegate bounded specialist work to isolated KillerOS subagents",
857
904
  promptGuidelines: [
858
- "Use subagent for clearly separable specialist work; prefer read-only scout, planner, or reviewer roles before the sequential writer.",
905
+ "Use subagent for clearly separable specialist work; prefer read-only scout, planner, reviewer, or security roles before a writer.",
859
906
  "Do not request multiple write-capable subagents in one parallel batch.",
907
+ "Every child can load relevant skills with read and can use web_search, source_check, fetch_content, and get_search_content for external research.",
908
+ "When the user names a model or thinking effort, pass model and thinking separately; use inherit when the active parent or role setting should decide.",
860
909
  ],
861
910
  parameters: SubagentParams,
862
911
  executionMode: "sequential",
@@ -895,7 +944,9 @@ export function registerSubagentTool(pi: ExtensionAPI, options: SubagentRuntimeO
895
944
  }
896
945
 
897
946
  const resolvedModels = new Map<string, ResolvedModel>();
898
- for (const name of new Set(requested)) resolvedModels.set(name, resolveAgentModel(roles.get(name)!, ctx));
947
+ for (const name of new Set(requested)) {
948
+ resolvedModels.set(name, resolveAgentModel(roles.get(name)!, ctx, params.model, params.thinking));
949
+ }
899
950
 
900
951
  const mode: SubagentDetails["mode"] = hasParallel ? "parallel" : hasChain ? "chain" : "single";
901
952
  const inputs: TaskInput[] = hasSingle
@@ -941,6 +992,8 @@ export function registerSubagentTool(pi: ExtensionAPI, options: SubagentRuntimeO
941
992
  step: results[index]!.step,
942
993
  model: resolvedModels.get(input.agent)!,
943
994
  signal,
995
+ webExtension: options.webExtension,
996
+ projectTrusted: ctx.isProjectTrusted(),
944
997
  spawnProcess,
945
998
  limits,
946
999
  onChange: (next) => {