pi-sage 0.2.0
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/.pi/extensions/sage/index.ts +659 -0
- package/.pi/extensions/sage/policy.ts +114 -0
- package/.pi/extensions/sage/runner.ts +461 -0
- package/.pi/extensions/sage/settings.ts +202 -0
- package/.pi/extensions/sage/tool-policy.ts +195 -0
- package/.pi/extensions/sage/types.ts +108 -0
- package/AGENTS.md +87 -0
- package/LICENSE +21 -0
- package/README.md +93 -0
- package/docs/SAGE_SPEC.md +490 -0
- package/docs/coding-standards.md +116 -0
- package/docs/installation-requirements.md +70 -0
- package/docs/interactive-e2e-harness.md +46 -0
- package/docs/testing-standards.md +175 -0
- package/package.json +62 -0
package/README.md
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# pi-sage
|
|
2
|
+
|
|
3
|
+
Interactive-only advisory Sage extension for Pi.
|
|
4
|
+
|
|
5
|
+
## Runtime requirements (for users)
|
|
6
|
+
|
|
7
|
+
- **Pi CLI** installed (`pi`) and available on PATH
|
|
8
|
+
- **Pi version**: `0.64.0+` recommended
|
|
9
|
+
- **Model access** configured in Pi (API keys / provider auth)
|
|
10
|
+
- **OS**: Linux/macOS/Windows supported
|
|
11
|
+
- **Optional**: `git` CLI for `git-review-readonly` profile
|
|
12
|
+
|
|
13
|
+
> Important: Sage spawns a nested `pi` subprocess for consultations, so `pi` must be invokable from the environment where Pi itself is running.
|
|
14
|
+
|
|
15
|
+
## Install
|
|
16
|
+
|
|
17
|
+
### Global install (all projects)
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
pi install pi-sage
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### Project-local install (current project only)
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
pi install -l pi-sage
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Then in Pi run:
|
|
30
|
+
|
|
31
|
+
```text
|
|
32
|
+
/reload
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Usage
|
|
36
|
+
|
|
37
|
+
- Ask naturally: “use Sage”, “get a second opinion”, etc.
|
|
38
|
+
- Or the model may invoke Sage autonomously when appropriate.
|
|
39
|
+
- Configure via:
|
|
40
|
+
|
|
41
|
+
```text
|
|
42
|
+
/sage-settings
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Tool profiles
|
|
46
|
+
|
|
47
|
+
- `read-only-lite` (default): `ls, glob, grep, read`
|
|
48
|
+
- `git-review-readonly`: adds restricted `bash` for allowlisted **read-only git** commands
|
|
49
|
+
- `none`
|
|
50
|
+
- `custom-read-only`
|
|
51
|
+
|
|
52
|
+
## Settings files (global + project override)
|
|
53
|
+
|
|
54
|
+
Sage settings are resolved in this order:
|
|
55
|
+
|
|
56
|
+
1. `.pi/sage-settings.json` (project override)
|
|
57
|
+
2. `~/.pi/agent/sage-settings.json` (global fallback)
|
|
58
|
+
3. built-in defaults
|
|
59
|
+
|
|
60
|
+
So yes: a global install can use global settings, and any project can override with its own `.pi/sage-settings.json`.
|
|
61
|
+
|
|
62
|
+
## Install scope and file layout
|
|
63
|
+
|
|
64
|
+
### Global install (`pi install npm:pi-sage`)
|
|
65
|
+
|
|
66
|
+
Pi writes package source to:
|
|
67
|
+
- `~/.pi/agent/settings.json`
|
|
68
|
+
|
|
69
|
+
Package files are installed globally by npm (`npm install -g`), typically under npm's global prefix, e.g.:
|
|
70
|
+
- Windows (typical): `%APPDATA%/npm/node_modules/pi-sage/`
|
|
71
|
+
- macOS/Linux (typical): `<npm-global-prefix>/lib/node_modules/pi-sage/`
|
|
72
|
+
|
|
73
|
+
Use `npm root -g` to see your exact global node_modules path.
|
|
74
|
+
|
|
75
|
+
### Project-local install (`pi install -l npm:pi-sage`)
|
|
76
|
+
|
|
77
|
+
Pi writes package source to:
|
|
78
|
+
- `.pi/settings.json`
|
|
79
|
+
|
|
80
|
+
Package files are installed under project-local Pi package storage:
|
|
81
|
+
- `.pi/npm/` (project-local package cache/install root)
|
|
82
|
+
|
|
83
|
+
### Settings files (independent of install scope)
|
|
84
|
+
|
|
85
|
+
- Global Sage settings: `~/.pi/agent/sage-settings.json`
|
|
86
|
+
- Project Sage settings: `.pi/sage-settings.json`
|
|
87
|
+
|
|
88
|
+
Project settings always override global settings for that project.
|
|
89
|
+
|
|
90
|
+
## Publishing note
|
|
91
|
+
|
|
92
|
+
Package name `sage` is already taken on npm.
|
|
93
|
+
`pi-sage` is currently available and is the intended publish name.
|
|
@@ -0,0 +1,490 @@
|
|
|
1
|
+
# Sage Specification (v0.1 Implementation Baseline)
|
|
2
|
+
|
|
3
|
+
> Status: **Locked baseline for implementation**. Behavioral changes require updates to this spec, tests, and standards docs in the same change set.
|
|
4
|
+
|
|
5
|
+
## 1) Summary
|
|
6
|
+
|
|
7
|
+
**Sage** is a built-in-style extension capability for Pi that gives the primary agent access to a high-reasoning, isolated subagent for second opinions on complex tasks.
|
|
8
|
+
|
|
9
|
+
Sage’s role is advisory: it provides analysis, feedback, and recommendations to the calling agent. Sage does **not** execute implementation work.
|
|
10
|
+
|
|
11
|
+
This spec targets a **final architecture** (not MVP): Sage is implemented as a **subagent process** (separate `pi` invocation), not as an inline single `complete(...)` call.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## 2) Goals
|
|
16
|
+
|
|
17
|
+
1. Let the primary agent consult Sage **autonomously** when useful.
|
|
18
|
+
2. Let the operator request Sage via **natural language** (no `/sage` command).
|
|
19
|
+
3. Provide `/sage-settings` for interactive configuration (model picker, budgets, limits, etc.).
|
|
20
|
+
4. Keep Sage expensive/slower by design, with explicit guardrails.
|
|
21
|
+
5. Preserve clear observability (what was asked, what Sage returned, cost/time/model used).
|
|
22
|
+
6. Keep Sage in an advisory lane (reasoning/recommendations), not a code-execution lane.
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## 3) Non-Goals (for v0.1)
|
|
27
|
+
|
|
28
|
+
1. Multi-Sage swarms or planner/reviewer pipelines.
|
|
29
|
+
2. Full UI dashboard beyond command-based configuration and standard tool rendering.
|
|
30
|
+
3. Automatic persistent learning from previous Sage calls.
|
|
31
|
+
4. Having Sage directly modify files, run shell commands, or perform orchestration actions.
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## 4) Core UX Requirements
|
|
36
|
+
|
|
37
|
+
### 4.1 Autonomous invocation (agentic behavior)
|
|
38
|
+
- The primary agent can call Sage **without operator explicitly asking**.
|
|
39
|
+
- Invocation is policy-gated (see Section 8).
|
|
40
|
+
- Typical autonomous trigger cases:
|
|
41
|
+
- ambiguous root-cause debugging,
|
|
42
|
+
- risky architectural trade-offs,
|
|
43
|
+
- conflicting evidence,
|
|
44
|
+
- high-impact refactors.
|
|
45
|
+
|
|
46
|
+
### 4.2 Operator invocation via natural language only
|
|
47
|
+
- Operator can request Sage by saying things like:
|
|
48
|
+
- “Use Sage”
|
|
49
|
+
- “Get a second opinion”
|
|
50
|
+
- “Consult a stronger reasoning model”
|
|
51
|
+
- There is **no `/sage` command**.
|
|
52
|
+
- The main agent satisfies such requests by deciding to call the Sage tool.
|
|
53
|
+
|
|
54
|
+
### 4.3 Settings command
|
|
55
|
+
- Provide `/sage-settings` command only.
|
|
56
|
+
- `/sage-settings` must include an interactive model selector sourced from Pi’s model registry (avoid manual exact-id typing).
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## 5) Architecture
|
|
61
|
+
|
|
62
|
+
## 5.1 Components
|
|
63
|
+
|
|
64
|
+
1. **Sage Extension** (`.pi/extensions/sage/index.ts`)
|
|
65
|
+
- Registers tool: `sage_consult`
|
|
66
|
+
- Registers command: `/sage-settings`
|
|
67
|
+
- Injects system-prompt guidance (autonomous use criteria)
|
|
68
|
+
- Enforces budgets/limits/policy
|
|
69
|
+
|
|
70
|
+
2. **Sage Runner** (subagent backend)
|
|
71
|
+
- Spawns isolated process:
|
|
72
|
+
- `pi --mode json -p --no-session --model <configured-model> ...`
|
|
73
|
+
- Provides dedicated Sage system prompt.
|
|
74
|
+
- Enforces **single-shot execution** per call (one request → one response).
|
|
75
|
+
- Enforces advisory tool policy (default `read-only-lite`: `ls,glob,grep,read`).
|
|
76
|
+
- Supports stricter `none` mode, optional `git-review-readonly` mode, and constrained custom read-only lists.
|
|
77
|
+
- Explicitly disables `sage_consult` inside Sage subprocess context to prevent subagent recursion.
|
|
78
|
+
|
|
79
|
+
3. **Sage Settings Store**
|
|
80
|
+
- Project: `.pi/sage-settings.json`
|
|
81
|
+
- Global fallback: `~/.pi/agent/sage-settings.json`
|
|
82
|
+
- Precedence: project > global > defaults
|
|
83
|
+
|
|
84
|
+
4. **Sage Prompt Policy Layer**
|
|
85
|
+
- Appends guidance to primary agent system prompt via `before_agent_start`.
|
|
86
|
+
- Adds tool `promptGuidelines` describing when to use Sage.
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## 5.2 Why subagent process (final approach)
|
|
91
|
+
|
|
92
|
+
- Hard context isolation.
|
|
93
|
+
- Independent model/thinking/tool profile.
|
|
94
|
+
- Better cancellation/error containment.
|
|
95
|
+
- Better extensibility (future chain/parallel Sage patterns).
|
|
96
|
+
- Stronger parity with Amp Oracle behavior.
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## 6) Tool Contract: `sage_consult`
|
|
101
|
+
|
|
102
|
+
## 6.1 Parameters
|
|
103
|
+
|
|
104
|
+
```ts
|
|
105
|
+
{
|
|
106
|
+
question: string; // Required: focused question to Sage
|
|
107
|
+
context?: string; // Optional: compressed relevant context
|
|
108
|
+
files?: string[]; // Optional: relevant file paths
|
|
109
|
+
evidence?: string[]; // Optional: logs/errors/observations
|
|
110
|
+
objective?: "debug" | "design" | "review" | "refactor" | "general";
|
|
111
|
+
urgency?: "low" | "medium" | "high";
|
|
112
|
+
force?: boolean; // If true, bypass autonomous budget gates (used for explicit user request)
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## 6.2 Result shape
|
|
117
|
+
|
|
118
|
+
Tool `content` returns human-readable markdown with analysis/recommendations for the primary agent.
|
|
119
|
+
Tool `details` returns structured metadata:
|
|
120
|
+
|
|
121
|
+
```ts
|
|
122
|
+
{
|
|
123
|
+
model: string;
|
|
124
|
+
reasoningLevel: "minimal"|"low"|"medium"|"high"|"xhigh";
|
|
125
|
+
latencyMs: number;
|
|
126
|
+
stopReason: string;
|
|
127
|
+
usage?: {
|
|
128
|
+
input: number;
|
|
129
|
+
output: number;
|
|
130
|
+
cacheRead: number;
|
|
131
|
+
cacheWrite: number;
|
|
132
|
+
totalTokens: number;
|
|
133
|
+
costTotal?: number;
|
|
134
|
+
};
|
|
135
|
+
toolUsage?: {
|
|
136
|
+
profile: "none"|"read-only-lite"|"git-review-readonly"|"custom-read-only";
|
|
137
|
+
callsUsed: number;
|
|
138
|
+
filesRead: number;
|
|
139
|
+
bytesRead: number;
|
|
140
|
+
};
|
|
141
|
+
policy: {
|
|
142
|
+
mode: "autonomous" | "user-requested";
|
|
143
|
+
allowedByContext: boolean;
|
|
144
|
+
contextReason?: string;
|
|
145
|
+
blockCode?: "ineligible-caller"|"non-interactive"|"ci-mode"|"rpc-role"|"subagent"|"unknown-context"|"tool-disallowed"|"path-denied"|"volume-cap";
|
|
146
|
+
allowedByBudget: boolean;
|
|
147
|
+
budgetReason?: string;
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## 7) Prompt / Behavior Design
|
|
155
|
+
|
|
156
|
+
## 7.1 Do we need system prompt patching?
|
|
157
|
+
|
|
158
|
+
**Yes.** For reliable autonomous behavior, the primary agent needs explicit instruction in its system prompt for:
|
|
159
|
+
- when to call Sage,
|
|
160
|
+
- when not to call Sage,
|
|
161
|
+
- how to summarize the consultation result,
|
|
162
|
+
- cost/speed awareness.
|
|
163
|
+
|
|
164
|
+
Without this, behavior is inconsistent and model-dependent.
|
|
165
|
+
|
|
166
|
+
## 7.2 Injected guidance (conceptual)
|
|
167
|
+
|
|
168
|
+
Primary agent guidance to append:
|
|
169
|
+
|
|
170
|
+
- You may call `sage_consult` for complex reasoning, debugging ambiguity, high-risk changes, or when user asks for second opinion.
|
|
171
|
+
- Do not overuse Sage for routine edits.
|
|
172
|
+
- Sage is an advisor, not an executor: use Sage for analysis/recommendations, then implement changes in the primary agent flow.
|
|
173
|
+
- Prefer one focused Sage question over multiple vague calls.
|
|
174
|
+
- After Sage returns, synthesize result and proceed with concrete next steps.
|
|
175
|
+
- If user explicitly asks for Sage/second opinion, prioritize making at least one Sage call unless blocked by hard safety limits.
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
## 8) Invocation Policy & Guardrails
|
|
180
|
+
|
|
181
|
+
## 8.1 Modes and caller scope
|
|
182
|
+
- `autonomousEnabled: boolean` (applies only in eligible interactive primary sessions; default: true there)
|
|
183
|
+
- `explicitRequestAlwaysAllowed: boolean` (default: true)
|
|
184
|
+
- `invocationScope: "interactive-primary-only"` (default and recommended)
|
|
185
|
+
- `sage_consult` is callable only from the top-level interactive primary agent session.
|
|
186
|
+
- `sage_consult` is blocked for non-interactive/CI contexts and RPC-orchestrated agent roles (e.g., supervisor, worker, reviewer, merger).
|
|
187
|
+
|
|
188
|
+
## 8.2 Soft limits (defaults)
|
|
189
|
+
Soft limits are policy/budget controls and are bypassable for explicit user requests (`force=true`).
|
|
190
|
+
- `maxCallsPerTurn: 1`
|
|
191
|
+
- `maxCallsPerSession: 6`
|
|
192
|
+
- `cooldownTurnsBetweenAutoCalls: 1`
|
|
193
|
+
- `maxQuestionChars: 6000`
|
|
194
|
+
- `maxContextChars: 20000`
|
|
195
|
+
|
|
196
|
+
## 8.3 Hard safety limits (non-bypassable)
|
|
197
|
+
- Disallowed caller context (not top-level interactive primary session).
|
|
198
|
+
- Model unavailable or no API key.
|
|
199
|
+
- Timeout exceeded (`timeoutMs`, default `120000`).
|
|
200
|
+
- Absolute cost cap exceeded (`maxEstimatedCostPerSession`, optional).
|
|
201
|
+
|
|
202
|
+
## 8.4 Explicit user request behavior
|
|
203
|
+
- If user explicitly requests Sage (“Sage”, “second opinion”, etc.), set `force=true` path to bypass soft limits.
|
|
204
|
+
- Hard safety limits and caller-scope restrictions still apply.
|
|
205
|
+
|
|
206
|
+
## 8.5 Recursion and interaction constraints
|
|
207
|
+
- Sage subprocesses are **not allowed** to invoke `sage_consult`.
|
|
208
|
+
- Every `sage_consult` call is **single-shot** (no multi-turn sub-conversation loop).
|
|
209
|
+
- Primary agent should ask one focused question and then continue execution after one Sage response.
|
|
210
|
+
|
|
211
|
+
## 8.6 Caller eligibility gate (concrete enforcement)
|
|
212
|
+
Implement a deterministic `isEligibleCaller(context)` check before any policy/budget logic.
|
|
213
|
+
|
|
214
|
+
Required conditions (all must be true):
|
|
215
|
+
1. `context.session.interactive === true`
|
|
216
|
+
2. `context.agent.role === "primary"`
|
|
217
|
+
3. `context.agent.isSubagent !== true`
|
|
218
|
+
4. `context.agent.isRpcOrchestrated !== true`
|
|
219
|
+
5. `context.runtime.mode !== "ci"`
|
|
220
|
+
|
|
221
|
+
Deny-by-default behavior:
|
|
222
|
+
- If any required context field is missing/unknown, block invocation with `blockCode: "unknown-context"`.
|
|
223
|
+
- Explicit user requests (`force=true`) do **not** bypass this gate.
|
|
224
|
+
|
|
225
|
+
Recommended pseudocode:
|
|
226
|
+
|
|
227
|
+
```ts
|
|
228
|
+
function isEligibleCaller(ctx: CallerContext): { ok: boolean; blockCode?: string; reason: string } {
|
|
229
|
+
if (!ctx || !ctx.session || !ctx.agent || !ctx.runtime) {
|
|
230
|
+
return { ok: false, blockCode: "unknown-context", reason: "Missing caller context" };
|
|
231
|
+
}
|
|
232
|
+
if (ctx.session.interactive !== true) {
|
|
233
|
+
return { ok: false, blockCode: "non-interactive", reason: "Sage allowed only in interactive sessions" };
|
|
234
|
+
}
|
|
235
|
+
if (ctx.runtime.mode === "ci") {
|
|
236
|
+
return { ok: false, blockCode: "ci-mode", reason: "Sage disabled in CI mode" };
|
|
237
|
+
}
|
|
238
|
+
if (ctx.agent.isRpcOrchestrated === true) {
|
|
239
|
+
return { ok: false, blockCode: "rpc-role", reason: "Sage disabled for RPC-orchestrated agents" };
|
|
240
|
+
}
|
|
241
|
+
if (ctx.agent.isSubagent === true) {
|
|
242
|
+
return { ok: false, blockCode: "subagent", reason: "Sage disabled for subagents" };
|
|
243
|
+
}
|
|
244
|
+
if (ctx.agent.role !== "primary") {
|
|
245
|
+
return { ok: false, blockCode: "ineligible-caller", reason: "Only primary agent may invoke Sage" };
|
|
246
|
+
}
|
|
247
|
+
return { ok: true, reason: "eligible" };
|
|
248
|
+
}
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
## 8.7 RPC role mapping guidance (orchestration frameworks)
|
|
252
|
+
For orchestrated multi-agent frameworks, map caller role metadata so these roles are always ineligible:
|
|
253
|
+
- `supervisor`
|
|
254
|
+
- `worker`
|
|
255
|
+
- `reviewer`
|
|
256
|
+
- `merger`
|
|
257
|
+
|
|
258
|
+
If runtime only provides a session name/string, derive role via conservative matching and deny on ambiguity.
|
|
259
|
+
|
|
260
|
+
---
|
|
261
|
+
|
|
262
|
+
## 9) Natural-Language Operator Invocation
|
|
263
|
+
|
|
264
|
+
No `/sage` command is provided.
|
|
265
|
+
|
|
266
|
+
Mechanism:
|
|
267
|
+
1. User asks naturally for second opinion.
|
|
268
|
+
2. Top-level interactive primary agent interprets request (with prompt guidance + tool availability).
|
|
269
|
+
3. If caller scope is eligible, primary agent calls `sage_consult`; otherwise Sage is blocked and the agent proceeds with best-effort local reasoning.
|
|
270
|
+
|
|
271
|
+
v0.1 decision:
|
|
272
|
+
- Rely on primary model instruction-following for explicit-request detection.
|
|
273
|
+
- Do **not** add a phrase-detection hook initially.
|
|
274
|
+
- Add a hook later only if testing shows missed explicit user requests.
|
|
275
|
+
|
|
276
|
+
---
|
|
277
|
+
|
|
278
|
+
## 10) `/sage-settings` Command Spec
|
|
279
|
+
|
|
280
|
+
## 10.1 Purpose
|
|
281
|
+
Interactive command to configure Sage runtime behavior.
|
|
282
|
+
|
|
283
|
+
## 10.2 Required settings
|
|
284
|
+
1. **Enabled**: on/off
|
|
285
|
+
2. **Autonomous mode**: on/off
|
|
286
|
+
3. **Sage model**: picker from `modelRegistry.getAvailable()`
|
|
287
|
+
4. **Reasoning level**: minimal/low/medium/high/xhigh
|
|
288
|
+
5. **Timeout**: ms/seconds
|
|
289
|
+
6. **Per-turn call limit**
|
|
290
|
+
7. **Per-session call limit**
|
|
291
|
+
8. **Cooldown between autonomous calls**
|
|
292
|
+
9. **Tool profile for Sage subagent**:
|
|
293
|
+
- none (strict advisor mode)
|
|
294
|
+
- read-only-lite (`ls,glob,grep,read`) **default**
|
|
295
|
+
- git-review-readonly (`ls,glob,grep,read,bash`) with strict allowlisted git read commands only
|
|
296
|
+
- custom read-only list (must exclude mutating/execution tools)
|
|
297
|
+
10. **Per-call max tool calls** (default: 10)
|
|
298
|
+
11. **Per-call max files read** (default: 8)
|
|
299
|
+
12. **Per-file max bytes** (default: 200KB)
|
|
300
|
+
13. **Per-call max total bytes read** (default: 1MB)
|
|
301
|
+
14. **Sensitive path denylist** (default on; configurable additions)
|
|
302
|
+
15. **Cost cap** (optional)
|
|
303
|
+
|
|
304
|
+
## 10.3 UX behavior
|
|
305
|
+
- Use selection UI instead of free-text where possible.
|
|
306
|
+
- Show currently active values and source (project/global/default).
|
|
307
|
+
- Save options to project or global scope (user choice in command flow).
|
|
308
|
+
- Include “Test Sage call” action to validate model/auth.
|
|
309
|
+
|
|
310
|
+
---
|
|
311
|
+
|
|
312
|
+
## 11) Security and Safety
|
|
313
|
+
|
|
314
|
+
1. Sage subprocess executes with same local permissions as Pi.
|
|
315
|
+
2. Sage subprocess must not have access to `sage_consult` (no autonomous agent recursion).
|
|
316
|
+
3. `sage_consult` must be disabled for RPC-orchestrated agents and non-interactive sessions.
|
|
317
|
+
4. Never pass secrets intentionally unless present in existing prompt/context.
|
|
318
|
+
|
|
319
|
+
### 11.1 Tool-access policy (advisor model)
|
|
320
|
+
1. Default Sage tool profile is `read-only-lite`: `ls,glob,grep,read`.
|
|
321
|
+
2. Allow `none` profile for stricter environments.
|
|
322
|
+
3. Custom profile must be read-only; mutating/execution tools are disallowed.
|
|
323
|
+
4. Explicitly disallow: `edit`, `write`, git-mutating tools, orchestration-control tools, and network/http tools.
|
|
324
|
+
5. Exception: `git-review-readonly` profile may allow `bash` only for allowlisted read-only git commands (e.g., status/diff/show/log/blame/rev-parse/branch --show-current).
|
|
325
|
+
|
|
326
|
+
### 11.2 Data-access and volume guardrails
|
|
327
|
+
1. Restrict reads to workspace/project roots.
|
|
328
|
+
2. Denylist sensitive paths by default (e.g., `.env*`, `*.pem`, `*.key`, `id_rsa*`, credential stores).
|
|
329
|
+
3. Enforce caps per Sage call:
|
|
330
|
+
- max tool calls: `10`
|
|
331
|
+
- max files read: `8`
|
|
332
|
+
- max bytes per file: `200KB`
|
|
333
|
+
- max total bytes read: `1MB`
|
|
334
|
+
4. Cap `grep`/`glob` result counts to avoid context explosion.
|
|
335
|
+
|
|
336
|
+
---
|
|
337
|
+
|
|
338
|
+
## 12) Observability
|
|
339
|
+
|
|
340
|
+
Each Sage call should expose:
|
|
341
|
+
- mode (autonomous vs user-requested),
|
|
342
|
+
- model + reasoning level,
|
|
343
|
+
- tool profile used (`none`, `read-only-lite`, `git-review-readonly`, or `custom-read-only`),
|
|
344
|
+
- elapsed time,
|
|
345
|
+
- token usage + cost (if available),
|
|
346
|
+
- refusal/error reason when blocked.
|
|
347
|
+
|
|
348
|
+
Tool rendering should keep concise collapsed view and richer expanded view.
|
|
349
|
+
|
|
350
|
+
---
|
|
351
|
+
|
|
352
|
+
## 13) Failure Handling
|
|
353
|
+
|
|
354
|
+
On Sage failure:
|
|
355
|
+
1. Return structured error in tool result (`isError: true`).
|
|
356
|
+
2. Primary agent continues without crashing.
|
|
357
|
+
3. If failure was during explicit user-requested consultation, agent should state that Sage failed and proceed with best-effort local reasoning.
|
|
358
|
+
|
|
359
|
+
On ineligible caller/context block:
|
|
360
|
+
1. Return structured non-crashing block result with `policy.allowedByContext=false`, `policy.blockCode`, and `policy.contextReason`.
|
|
361
|
+
2. Do not spawn Sage subprocess.
|
|
362
|
+
3. Primary agent proceeds with best-effort local reasoning and briefly explains why Sage was unavailable.
|
|
363
|
+
|
|
364
|
+
On tool-policy/data-policy block:
|
|
365
|
+
1. Return structured non-crashing block result with `policy.blockCode` (`tool-disallowed`, `path-denied`, or `volume-cap`).
|
|
366
|
+
2. Do not escalate privileges or relax guardrails automatically.
|
|
367
|
+
3. Continue with available context and provide best-effort recommendations.
|
|
368
|
+
|
|
369
|
+
---
|
|
370
|
+
|
|
371
|
+
## 14) Acceptance Criteria (Draft)
|
|
372
|
+
|
|
373
|
+
1. Primary agent autonomously invokes Sage at least once in a complex debug scenario without user explicitly asking (interactive top-level primary session).
|
|
374
|
+
2. In CI/non-interactive mode, Sage invocation is blocked (non-bypassable caller-scope gate).
|
|
375
|
+
3. RPC-orchestrated agents (supervisor/worker/reviewer/merger) cannot invoke `sage_consult`.
|
|
376
|
+
4. User phrase “get a second opinion” in an eligible interactive primary session causes at least one Sage consultation in same task flow.
|
|
377
|
+
5. Explicit user-requested Sage consultation can bypass soft limits, but still respects hard safety limits and caller-scope restrictions.
|
|
378
|
+
6. No `/sage` command exists.
|
|
379
|
+
7. `/sage-settings` lists available models interactively and persists selected model.
|
|
380
|
+
8. Default Sage tool profile is `read-only-lite` (`ls,glob,grep,read`), with `none`, `git-review-readonly`, and constrained custom read-only options available.
|
|
381
|
+
9. Sage tool profile blocks mutating/execution/network/orchestration tools (including `edit`, `write`, and `sage_consult`), except allowlisted git-read bash commands in `git-review-readonly`.
|
|
382
|
+
10. Sage enforces per-call guardrails (max tool calls/files/bytes and capped `grep`/`glob` output).
|
|
383
|
+
11. Sage calls obey hard safety limits (caller context, model/auth availability, timeout, optional absolute cost cap).
|
|
384
|
+
12. Sage subprocess cannot invoke `sage_consult` (no recursion).
|
|
385
|
+
13. Sage consultations are single-shot per call.
|
|
386
|
+
14. Tool output shows model/latency/usage metadata, including tool profile/tool usage and block reason when context is ineligible.
|
|
387
|
+
|
|
388
|
+
---
|
|
389
|
+
|
|
390
|
+
## 15) Resolved Decisions (from review)
|
|
391
|
+
|
|
392
|
+
1. Explicit user requests should bypass all soft limits.
|
|
393
|
+
2. Sage invocation is restricted to interactive top-level primary sessions.
|
|
394
|
+
3. Autonomous Sage calls are disabled in CI/non-interactive contexts via caller-scope gate.
|
|
395
|
+
4. Sage is single-shot per call (no multi-turn Sage sub-conversations in v0.1).
|
|
396
|
+
5. Start with model interpretation for explicit-request detection; add phrase-detection hook only if testing reveals reliability issues.
|
|
397
|
+
6. Sage subagents cannot invoke other Sage subagents (no recursion).
|
|
398
|
+
7. Sage is advisory-only and should not perform implementation actions.
|
|
399
|
+
8. Default Sage tool profile is `read-only-lite` (`ls,glob,grep,read`) with strict guardrails; optional `git-review-readonly` exists for code-review workflows.
|
|
400
|
+
|
|
401
|
+
---
|
|
402
|
+
|
|
403
|
+
## 16) Implementation Order (Suggested)
|
|
404
|
+
|
|
405
|
+
1. Build settings store + `/sage-settings` command.
|
|
406
|
+
2. Implement `sage_consult` subprocess runner + structured result metadata.
|
|
407
|
+
3. Add hard caller-context gate (interactive top-level primary only; block CI/RPC-orchestrated roles).
|
|
408
|
+
4. Implement advisory tool policy (`read-only-lite`, `none`, `git-review-readonly`, constrained custom) and hard denylist for mutating/execution tools.
|
|
409
|
+
5. Add data-volume guardrails (tool-call/file/byte caps + `grep`/`glob` result caps + sensitive-path denylist).
|
|
410
|
+
6. Add system-prompt guidance injection for autonomous invocation.
|
|
411
|
+
7. Add budget gates and policy telemetry.
|
|
412
|
+
8. Add rendering polish and acceptance tests.
|
|
413
|
+
9. Add a caller-context + tool-policy test matrix (interactive primary allowed; CI blocked; supervisor/worker/reviewer/merger blocked; subagent blocked; unknown context blocked; disallowed tool attempts blocked).
|
|
414
|
+
|
|
415
|
+
---
|
|
416
|
+
|
|
417
|
+
## 17) Concrete Implementation Checklist (`.pi/extensions/sage/index.ts`)
|
|
418
|
+
|
|
419
|
+
### 17.1 Types and constants
|
|
420
|
+
- [ ] Add `ToolProfile = "none" | "read-only-lite" | "git-review-readonly" | "custom-read-only"`.
|
|
421
|
+
- [ ] Add `BlockCode = "ineligible-caller" | "non-interactive" | "ci-mode" | "rpc-role" | "subagent" | "unknown-context" | "tool-disallowed" | "path-denied" | "volume-cap"`.
|
|
422
|
+
- [ ] Add `CallerContext` type:
|
|
423
|
+
- `session.interactive: boolean`
|
|
424
|
+
- `agent.role: string`
|
|
425
|
+
- `agent.isSubagent: boolean`
|
|
426
|
+
- `agent.isRpcOrchestrated: boolean`
|
|
427
|
+
- `runtime.mode: string`
|
|
428
|
+
- [ ] Add `ToolPolicySettings` type:
|
|
429
|
+
- `profile`, `customAllowedTools`
|
|
430
|
+
- `maxToolCalls`, `maxFilesRead`, `maxBytesPerFile`, `maxTotalBytesRead`
|
|
431
|
+
- `sensitivePathDenylist`
|
|
432
|
+
- [ ] Add constants:
|
|
433
|
+
- `READ_ONLY_LITE_TOOLS = ["ls", "glob", "grep", "read"]`
|
|
434
|
+
- `HARD_DISALLOWED_TOOLS = ["edit", "write", "bash", "sage_consult"]`
|
|
435
|
+
- default caps: `10` calls, `8` files, `200KB/file`, `1MB total`.
|
|
436
|
+
|
|
437
|
+
### 17.2 Policy/guard functions
|
|
438
|
+
- [ ] `resolveCallerContext(runCtx): CallerContext | null`
|
|
439
|
+
- [ ] `isEligibleCaller(ctx): { ok: boolean; blockCode?: BlockCode; reason: string }`
|
|
440
|
+
- [ ] `resolveToolPolicy(settings): ResolvedToolPolicy`
|
|
441
|
+
- [ ] `validateCustomToolList(tools): { ok: boolean; disallowed?: string[] }`
|
|
442
|
+
- [ ] `isPathAllowed(path, workspaceRoots, denylist): { ok: boolean; reason?: string }`
|
|
443
|
+
- [ ] `checkVolumeCaps(usage, caps): { ok: boolean; blockCode?: "volume-cap"; reason?: string }`
|
|
444
|
+
- [ ] `makeBlockedResult(blockCode, reason, partialDetails?)` helper for consistent tool responses.
|
|
445
|
+
|
|
446
|
+
### 17.3 `sage_consult` handler flow
|
|
447
|
+
- [ ] Step 1: Load merged settings (project > global > defaults).
|
|
448
|
+
- [ ] Step 2: Resolve caller context.
|
|
449
|
+
- [ ] Step 3: Apply hard caller gate (`isEligibleCaller`) **before** budget checks.
|
|
450
|
+
- [ ] Step 4: If blocked, return structured result with:
|
|
451
|
+
- `policy.allowedByContext=false`
|
|
452
|
+
- `policy.blockCode`
|
|
453
|
+
- `policy.contextReason`
|
|
454
|
+
- [ ] Step 5: Apply soft-budget gate (respect `force=true` bypass for explicit user requests).
|
|
455
|
+
- [ ] Step 6: Build Sage subprocess invocation with restricted tool manifest based on resolved tool profile.
|
|
456
|
+
- [ ] Step 7: Run single-shot subprocess with timeout.
|
|
457
|
+
- [ ] Step 8: Collect model/latency/tokens + `toolUsage` + policy metadata.
|
|
458
|
+
- [ ] Step 9: Return advisory markdown (`content`) + structured `details`.
|
|
459
|
+
|
|
460
|
+
### 17.4 Subprocess/tool isolation
|
|
461
|
+
- [ ] Ensure Sage subprocess tool registry excludes `sage_consult`.
|
|
462
|
+
- [ ] Ensure mutating/execution/network/orchestration tools are unavailable to Sage profile resolution.
|
|
463
|
+
- [ ] Ensure custom profile cannot include tools outside read-only allowlist.
|
|
464
|
+
|
|
465
|
+
### 17.5 `/sage-settings` command wiring
|
|
466
|
+
- [ ] Add profile picker: `none`, `read-only-lite`, `git-review-readonly`, `custom-read-only`.
|
|
467
|
+
- [ ] For `custom-read-only`, show multi-select constrained to read-only tools.
|
|
468
|
+
- [ ] Add numeric inputs for guardrails (tool calls/files/bytes).
|
|
469
|
+
- [ ] Add editable sensitive-path denylist (with safe defaults preloaded).
|
|
470
|
+
- [ ] Show effective scope/source (project/global/default) and save target.
|
|
471
|
+
|
|
472
|
+
### 17.6 Test checklist
|
|
473
|
+
- [ ] Unit: `isEligibleCaller` allows only interactive primary; blocks CI, non-interactive, subagent, rpc-role, unknown context.
|
|
474
|
+
- [ ] Unit: `resolveToolPolicy` defaults to `read-only-lite` and strips/blocks disallowed custom tools.
|
|
475
|
+
- [ ] Unit: `isPathAllowed` blocks denylisted paths and non-workspace paths.
|
|
476
|
+
- [ ] Unit: `checkVolumeCaps` trips correctly on call/file/byte overages.
|
|
477
|
+
- [ ] Integration: explicit user request with `force=true` bypasses soft limits but not caller gate.
|
|
478
|
+
- [ ] Integration: RPC roles (`supervisor`, `worker`, `reviewer`, `merger`) receive structured blocked result.
|
|
479
|
+
- [ ] Integration: Sage subprocess cannot call `sage_consult` (recursion test).
|
|
480
|
+
- [ ] Integration: tool usage metadata is present in successful responses.
|
|
481
|
+
- [ ] Integration: blocked results include `blockCode` + human-readable reason.
|
|
482
|
+
|
|
483
|
+
### 17.7 Suggested file split (optional)
|
|
484
|
+
- `index.ts` (extension registration + tool/command wiring)
|
|
485
|
+
- `policy.ts` (caller gate, budget gate, block result helpers)
|
|
486
|
+
- `tool-policy.ts` (profile resolution, path/volume guardrails)
|
|
487
|
+
- `settings.ts` (load/merge/validate settings)
|
|
488
|
+
- `runner.ts` (single-shot subprocess execution)
|
|
489
|
+
- `types.ts` (shared interfaces/enums)
|
|
490
|
+
- `__tests__/...` (unit + integration suites)
|