@ssweens/pi-handoff 1.0.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/LICENSE +21 -0
- package/README.md +224 -0
- package/extensions/handoff.ts +365 -0
- package/extensions/session-query.ts +219 -0
- package/package.json +32 -0
- package/screenshot.png +0 -0
- package/skills/pi-session-query/SKILL.md +57 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ssweens
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
# pi-handoff
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
pi install @ssweens/pi-handoff
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
Context handoff extension for [pi](https://github.com/badlogic/pi-mono). Transfer context to a new session with a structured summary — the agent can trigger handoffs, or they happen automatically on compaction.
|
|
10
|
+
|
|
11
|
+
## Features
|
|
12
|
+
|
|
13
|
+
- **User preview/editing** — Review and edit the handoff draft before submission
|
|
14
|
+
- **Agent-callable handoff tool** — The model can initiate handoffs when explicitly asked
|
|
15
|
+
- **Auto-handoff on compaction** — Uses Pi's preparation data so summaries won't overflow
|
|
16
|
+
- **Structured format** — Bullet list with code pointers (path:line or path#Symbol)
|
|
17
|
+
- **Parent session query** — `session_query` tool for looking up details from parent sessions
|
|
18
|
+
- **Auto-inject skill** — Detects `Parent session:` references and enables query instructions automatically
|
|
19
|
+
- **System prompt hints** — The model knows about handoffs and suggests them proactively
|
|
20
|
+
- **Session naming** — New sessions named based on handoff goal
|
|
21
|
+
|
|
22
|
+
## Installation
|
|
23
|
+
|
|
24
|
+
### From npm
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
pi install @ssweens/pi-handoff
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### From git (global)
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
pi install git:github.com/ssweens/pi-handoff
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### From git (project-local)
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
pi install -l git:github.com/ssweens/pi-handoff
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Try without installing
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
pi -e git:github.com/ssweens/pi-handoff
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### From local path (development)
|
|
49
|
+
|
|
50
|
+
Add to your settings (`~/.pi/agent/settings.json` or `.pi/settings.json`):
|
|
51
|
+
|
|
52
|
+
```json
|
|
53
|
+
{
|
|
54
|
+
"packages": [
|
|
55
|
+
"/path/to/pi-handoff"
|
|
56
|
+
]
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Features
|
|
61
|
+
|
|
62
|
+
### `/handoff <goal>` — Context Transfer
|
|
63
|
+
|
|
64
|
+
When your conversation gets long or you want to branch off to a focused task:
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
/handoff now implement this for teams as well
|
|
68
|
+
/handoff execute phase one of the plan
|
|
69
|
+
/handoff check other places that need this fix
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
This:
|
|
73
|
+
1. Analyzes your current conversation
|
|
74
|
+
2. Generates a structured handoff summary with:
|
|
75
|
+
- Key decisions and approaches (as bullet points)
|
|
76
|
+
- Relevant files with code pointers
|
|
77
|
+
- Clear task description based on your goal
|
|
78
|
+
3. Opens an editor for you to review/modify the draft
|
|
79
|
+
4. Creates a new session with parent tracking
|
|
80
|
+
5. Sets up the prompt ready to submit
|
|
81
|
+
|
|
82
|
+
### Agent-Initiated Handoff
|
|
83
|
+
|
|
84
|
+
The model can also create handoffs when you explicitly ask:
|
|
85
|
+
|
|
86
|
+
```
|
|
87
|
+
"Please hand this off to a new session to implement the fix"
|
|
88
|
+
"Create a handoff to execute phase one"
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
The agent uses the `handoff` tool, which defers the handoff until after the current turn completes (so the tool result is properly recorded in the old session). In tool/hook contexts, it creates a new session file and rebases the active agent context to start at the handoff prompt, so the next turn runs with the handed-off context instead of the old oversized history.
|
|
92
|
+
|
|
93
|
+
### System Prompt Awareness
|
|
94
|
+
|
|
95
|
+
The extension injects handoff awareness into the system prompt. The model knows:
|
|
96
|
+
- `/handoff` exists and when to suggest it
|
|
97
|
+
- Handoffs after planning sessions are especially effective — clear context and start fresh with the plan
|
|
98
|
+
- At high context usage, it should suggest a handoff rather than losing context
|
|
99
|
+
|
|
100
|
+
### Auto-Handoff on Compaction
|
|
101
|
+
|
|
102
|
+
When auto-compaction triggers (context exceeds the compaction threshold), the extension intercepts and offers a choice: **handoff to a new session** or **compact in place**.
|
|
103
|
+
|
|
104
|
+
If you choose handoff:
|
|
105
|
+
1. A summary is generated (same structured format as `/handoff`)
|
|
106
|
+
2. You review/edit the handoff prompt
|
|
107
|
+
3. A new session is created with the summary, old session preserved
|
|
108
|
+
4. The agent continues in the new session
|
|
109
|
+
|
|
110
|
+
If you decline, normal compaction proceeds as usual.
|
|
111
|
+
|
|
112
|
+
**Requires `compaction.enabled = true`** (the default). When auto-compaction is disabled, this hook never fires — use `/handoff` manually instead.
|
|
113
|
+
|
|
114
|
+
### `session_query` Tool — Cross-Session Context
|
|
115
|
+
|
|
116
|
+
The model can query parent sessions for details not in the handoff summary:
|
|
117
|
+
|
|
118
|
+
```typescript
|
|
119
|
+
session_query("/path/to/parent/session.jsonl", "What files were modified?")
|
|
120
|
+
session_query("/path/to/parent/session.jsonl", "What approach was chosen for authentication?")
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
**Auto-injection:** When a user message contains a `**Parent session:**` reference, the extension prepends `/skill:pi-session-query` inline with the prompt body (single-submit flow, no extra Enter round-trip). No manual directive needed in handoff prompts.
|
|
124
|
+
|
|
125
|
+
**Size guard:** Large parent sessions are truncated (keeping the most recent context) to prevent exceeding context limits during the query.
|
|
126
|
+
|
|
127
|
+
## Handoff Format
|
|
128
|
+
|
|
129
|
+
Generated handoffs follow a structured format adapted from Pi's compaction system, filtered through the user's stated goal:
|
|
130
|
+
|
|
131
|
+
```markdown
|
|
132
|
+
# <goal>
|
|
133
|
+
|
|
134
|
+
**Parent session:** `/path/to/session.jsonl`
|
|
135
|
+
|
|
136
|
+
## Goal
|
|
137
|
+
What the user wants to accomplish in the new thread.
|
|
138
|
+
|
|
139
|
+
## Key Decisions
|
|
140
|
+
- **Decision 1**: Rationale (path/to/file.ts:42)
|
|
141
|
+
- **Decision 2**: Rationale
|
|
142
|
+
|
|
143
|
+
## Constraints & Preferences
|
|
144
|
+
- Requirements or preferences the user stated
|
|
145
|
+
|
|
146
|
+
## Progress
|
|
147
|
+
### Done
|
|
148
|
+
- [x] Completed work relevant to the goal
|
|
149
|
+
|
|
150
|
+
### In Progress
|
|
151
|
+
- [ ] Partially completed work
|
|
152
|
+
|
|
153
|
+
### Blocked
|
|
154
|
+
- Open issues or blockers
|
|
155
|
+
|
|
156
|
+
## Files
|
|
157
|
+
- path/to/file1.ts (modified)
|
|
158
|
+
- path/to/file2.ts (read)
|
|
159
|
+
|
|
160
|
+
## Task
|
|
161
|
+
Clear, actionable next steps based on the goal.
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
The `/skill:pi-session-query` directive is auto-injected when this prompt is submitted (detected via the `**Parent session:**` marker).
|
|
165
|
+
|
|
166
|
+
## Architecture Comparison
|
|
167
|
+
|
|
168
|
+
| Feature | pi-amplike | mina | pi-handoff |
|
|
169
|
+
|---------|-----------|------|------------|
|
|
170
|
+
| `/handoff` command | ✅ | ✅ | ✅ |
|
|
171
|
+
| Agent-callable tool | ✅ | ❌ | ✅ |
|
|
172
|
+
| User preview/edit | ❌ | ✅ | ✅ |
|
|
173
|
+
| Auto-handoff on compact | ❌ | ❌ | ✅ |
|
|
174
|
+
| Parent query tool | ✅ | ✅ | ✅ |
|
|
175
|
+
| Structured bullets | ❌ | ✅ | ✅ |
|
|
176
|
+
| Code pointers | ❌ | ✅ | ✅ |
|
|
177
|
+
| Auto-detect parent ref | ❌ | ✅ | ✅ |
|
|
178
|
+
| System prompt hints | ❌ | ✅ | ✅ |
|
|
179
|
+
| Session naming | ❌ | ❌ | ✅ |
|
|
180
|
+
| Query size guard | ❌ | ✅ | ✅ |
|
|
181
|
+
| Deferred tool switch | ✅ | N/A | ✅ |
|
|
182
|
+
|
|
183
|
+
## How It Differs from Compaction
|
|
184
|
+
|
|
185
|
+
| | Compaction (`/compact`) | Handoff (`/handoff`) | Auto-Handoff |
|
|
186
|
+
|---|---|---|---|
|
|
187
|
+
| **Purpose** | Reduce context size | Transfer to focused task | Context full → new session |
|
|
188
|
+
| **Trigger** | Automatic or `/compact` | User types `/handoff` | Intercepts auto-compaction |
|
|
189
|
+
| **Continues** | Same session | New session | New session |
|
|
190
|
+
| **Context** | Lossy summary | Goal-directed summary | Goal-directed summary |
|
|
191
|
+
| **Parent access** | Lost | Queryable via `session_query` | Queryable via `session_query` |
|
|
192
|
+
| **Use case** | General context overflow | Task branching | Preserve old session on overflow |
|
|
193
|
+
|
|
194
|
+
## Session Navigation
|
|
195
|
+
|
|
196
|
+
Use pi's built-in `/resume` command to switch between sessions. Handoff creates sessions with descriptive names based on your goal.
|
|
197
|
+
|
|
198
|
+
## Components
|
|
199
|
+
|
|
200
|
+
| Component | Type | Description |
|
|
201
|
+
|-----------|------|-------------|
|
|
202
|
+
| [handoff.ts](extensions/handoff.ts) | Extension | `/handoff` command, `handoff` tool, auto-handoff on compaction, system prompt hints |
|
|
203
|
+
| [session-query.ts](extensions/session-query.ts) | Extension | `session_query` tool for the model (with size guard) |
|
|
204
|
+
| [pi-session-query/SKILL.md](skills/pi-session-query/SKILL.md) | Skill | Instructions for using `session_query` |
|
|
205
|
+
|
|
206
|
+
## Configuration
|
|
207
|
+
|
|
208
|
+
No configuration required. The extension uses your current model for both handoff generation and session queries.
|
|
209
|
+
|
|
210
|
+
### Optional: Dedicated Query Model
|
|
211
|
+
|
|
212
|
+
To use a smaller/faster model for session queries (reducing cost), you can modify `session-query.ts` to use a different model:
|
|
213
|
+
|
|
214
|
+
```typescript
|
|
215
|
+
// In session-query.ts execute function, replace:
|
|
216
|
+
const model = ctx.model;
|
|
217
|
+
|
|
218
|
+
// With a specific model lookup:
|
|
219
|
+
const model = ctx.modelRegistry.find("anthropic", "claude-3-haiku") ?? ctx.model;
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
## License
|
|
223
|
+
|
|
224
|
+
MIT
|
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Handoff Extension
|
|
3
|
+
*
|
|
4
|
+
* Transfers conversation context to a new focused session via:
|
|
5
|
+
* - /handoff <goal> command
|
|
6
|
+
* - Agent-callable handoff tool
|
|
7
|
+
* - Auto-handoff option when Pi triggers compaction
|
|
8
|
+
*
|
|
9
|
+
* The compaction hook uses Pi's preparation data (messagesToSummarize,
|
|
10
|
+
* previousSummary) instead of the full conversation, so the summary
|
|
11
|
+
* generation won't overflow the context window.
|
|
12
|
+
*
|
|
13
|
+
* Usage:
|
|
14
|
+
* /handoff now implement this for teams as well
|
|
15
|
+
* /handoff execute phase one of the plan
|
|
16
|
+
* /handoff check other places that need this fix
|
|
17
|
+
*
|
|
18
|
+
* The generated prompt appears as a draft in the editor for review/editing.
|
|
19
|
+
* The agent can also invoke the handoff tool when the user explicitly requests it.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import { complete, type Message } from "@mariozechner/pi-ai";
|
|
23
|
+
import type { ExtensionAPI, ExtensionContext, SessionEntry } from "@mariozechner/pi-coding-agent";
|
|
24
|
+
import { BorderedLoader, convertToLlm, serializeConversation } from "@mariozechner/pi-coding-agent";
|
|
25
|
+
import { Type } from "@sinclair/typebox";
|
|
26
|
+
|
|
27
|
+
// Store pending handoff text to be set in new session after switch
|
|
28
|
+
// Key: parent session file path, Value: handoff text to set in editor
|
|
29
|
+
const pendingHandoffText = new Map<string, string>();
|
|
30
|
+
|
|
31
|
+
// Handoff generation system prompt.
|
|
32
|
+
//
|
|
33
|
+
// Combines Pi's structured compaction format (Goal, Progress, Decisions,
|
|
34
|
+
// Constraints) with handoff-specific goal filtering, code pointers from
|
|
35
|
+
// mina, and an explicit Task section.
|
|
36
|
+
//
|
|
37
|
+
// Key differences from Pi compaction:
|
|
38
|
+
// - Goal-directed: everything is filtered through the user's stated goal
|
|
39
|
+
// - Code pointers: path:line and path#Symbol references in context
|
|
40
|
+
// - Task section: actionable next steps framed by the goal
|
|
41
|
+
// - Anti-continuation guard: prevent the summarizer from responding to the history
|
|
42
|
+
const SYSTEM_PROMPT = `You are a context transfer assistant. Read the conversation and produce a structured handoff summary for the stated goal. The new thread must be able to proceed without the old conversation.
|
|
43
|
+
|
|
44
|
+
Do NOT continue the conversation. Do NOT respond to any questions in the history. ONLY output the structured summary.
|
|
45
|
+
|
|
46
|
+
Use this EXACT format:
|
|
47
|
+
|
|
48
|
+
## Goal
|
|
49
|
+
[The user's goal for the new thread — what they want to accomplish.]
|
|
50
|
+
|
|
51
|
+
## Key Decisions
|
|
52
|
+
- **[Decision]**: [Brief rationale]
|
|
53
|
+
- Use code pointers (path/to/file.ts:42 or path/to/file.ts#functionName) where relevant
|
|
54
|
+
|
|
55
|
+
## Constraints & Preferences
|
|
56
|
+
- [Any requirements, constraints, or preferences the user stated]
|
|
57
|
+
- [Or "(none)" if none were mentioned]
|
|
58
|
+
|
|
59
|
+
## Progress
|
|
60
|
+
### Done
|
|
61
|
+
- [x] [Completed work relevant to the goal]
|
|
62
|
+
|
|
63
|
+
### In Progress
|
|
64
|
+
- [ ] [Partially completed work]
|
|
65
|
+
|
|
66
|
+
### Blocked
|
|
67
|
+
- [Open issues or blockers, if any]
|
|
68
|
+
|
|
69
|
+
## Files
|
|
70
|
+
- path/to/file1.ts (modified)
|
|
71
|
+
- path/to/file2.ts (read)
|
|
72
|
+
|
|
73
|
+
## Task
|
|
74
|
+
[Clear, actionable description of what to do next based on the goal. Ordered steps if appropriate.]
|
|
75
|
+
|
|
76
|
+
Rules:
|
|
77
|
+
- Be concise. Every bullet earns its place.
|
|
78
|
+
- Preserve exact file paths, function names, and error messages.
|
|
79
|
+
- Only include information relevant to the stated goal — discard unrelated context.
|
|
80
|
+
- Output the formatted content only. No preamble, no filler.`;
|
|
81
|
+
|
|
82
|
+
// System prompt fragment injected via before_agent_start.
|
|
83
|
+
// Teaches the model about handoffs so it can suggest them proactively.
|
|
84
|
+
const HANDOFF_SYSTEM_HINT = `
|
|
85
|
+
## Handoff
|
|
86
|
+
|
|
87
|
+
Use \`/handoff <goal>\` to transfer context to a new focused session.
|
|
88
|
+
Handoffs are especially effective after planning — clear the context and start a new session with the plan you just created.
|
|
89
|
+
At high context usage, suggest a handoff rather than losing important context.`;
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Generate a session name from the goal (slug format)
|
|
93
|
+
*/
|
|
94
|
+
function goalToSessionName(goal: string): string {
|
|
95
|
+
return goal
|
|
96
|
+
.toLowerCase()
|
|
97
|
+
.replace(/[^a-z0-9\s-]/g, "")
|
|
98
|
+
.trim()
|
|
99
|
+
.replace(/\s+/g, "-")
|
|
100
|
+
.slice(0, 50);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Handoff modes:
|
|
105
|
+
* - "command": User-initiated via /handoff
|
|
106
|
+
* - "tool": Agent-initiated via handoff tool
|
|
107
|
+
* - "compactHook": Triggered from session_before_compact
|
|
108
|
+
*
|
|
109
|
+
* All modes follow the same flow: generate summary → editor review → new session → input box → user sends
|
|
110
|
+
*/
|
|
111
|
+
type HandoffMode = "command" | "tool" | "compactHook";
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Core handoff logic shared by the /handoff command, the handoff tool,
|
|
115
|
+
* and the auto-handoff compaction hook.
|
|
116
|
+
*
|
|
117
|
+
* Returns an error string on failure, or undefined on success.
|
|
118
|
+
*/
|
|
119
|
+
async function performHandoff(
|
|
120
|
+
pi: ExtensionAPI,
|
|
121
|
+
ctx: ExtensionContext,
|
|
122
|
+
goal: string,
|
|
123
|
+
mode: HandoffMode = "command",
|
|
124
|
+
preBuiltContext?: string,
|
|
125
|
+
): Promise<string | undefined> {
|
|
126
|
+
if (!ctx.hasUI) {
|
|
127
|
+
return "Handoff requires interactive mode.";
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (!ctx.model) {
|
|
131
|
+
return "No model selected.";
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
let conversationText: string;
|
|
135
|
+
|
|
136
|
+
if (preBuiltContext) {
|
|
137
|
+
// compactHook: context already built from preparation data
|
|
138
|
+
conversationText = preBuiltContext;
|
|
139
|
+
} else {
|
|
140
|
+
// command/tool: gather full conversation (context isn't full yet)
|
|
141
|
+
const branch = ctx.sessionManager.getBranch();
|
|
142
|
+
const messages = branch
|
|
143
|
+
.filter((entry): entry is SessionEntry & { type: "message" } => entry.type === "message")
|
|
144
|
+
.map((entry) => entry.message);
|
|
145
|
+
|
|
146
|
+
if (messages.length === 0) {
|
|
147
|
+
return "No conversation to hand off.";
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
conversationText = serializeConversation(convertToLlm(messages));
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const currentSessionFile = ctx.sessionManager.getSessionFile();
|
|
154
|
+
|
|
155
|
+
// Generate the handoff prompt with loader UI
|
|
156
|
+
const result = await ctx.ui.custom<string | null>((tui, theme, _kb, done) => {
|
|
157
|
+
const loader = new BorderedLoader(tui, theme, `Generating handoff summary...`);
|
|
158
|
+
loader.onAbort = () => done(null);
|
|
159
|
+
|
|
160
|
+
const doGenerate = async () => {
|
|
161
|
+
const apiKey = await ctx.modelRegistry.getApiKey(ctx.model!);
|
|
162
|
+
|
|
163
|
+
const userMessage: Message = {
|
|
164
|
+
role: "user",
|
|
165
|
+
content: [
|
|
166
|
+
{
|
|
167
|
+
type: "text",
|
|
168
|
+
text: `## Conversation History\n\n${conversationText}\n\n## Goal for New Thread\n\n${goal}`,
|
|
169
|
+
},
|
|
170
|
+
],
|
|
171
|
+
timestamp: Date.now(),
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
const response = await complete(
|
|
175
|
+
ctx.model!,
|
|
176
|
+
{ systemPrompt: SYSTEM_PROMPT, messages: [userMessage] },
|
|
177
|
+
{ apiKey, signal: loader.signal },
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
if (response.stopReason === "aborted") {
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return response.content
|
|
185
|
+
.filter((c): c is { type: "text"; text: string } => c.type === "text")
|
|
186
|
+
.map((c) => c.text)
|
|
187
|
+
.join("\n");
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
doGenerate()
|
|
191
|
+
.then(done)
|
|
192
|
+
.catch((err) => {
|
|
193
|
+
console.error("Handoff generation failed:", err);
|
|
194
|
+
done(null);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
return loader;
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
if (result === null) {
|
|
201
|
+
return "Handoff cancelled.";
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Build the full prompt with parent reference
|
|
205
|
+
let fullPrompt = `# ${goal}\n\n`;
|
|
206
|
+
|
|
207
|
+
if (currentSessionFile) {
|
|
208
|
+
fullPrompt += `**Parent session:** \`${currentSessionFile}\`\n\n`;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
fullPrompt += result;
|
|
212
|
+
|
|
213
|
+
// Prepend session-query skill if parent session present
|
|
214
|
+
const messageToSend = /\*\*Parent session:\*\*/.test(fullPrompt)
|
|
215
|
+
? `/skill:pi-session-query ${fullPrompt}`
|
|
216
|
+
: fullPrompt;
|
|
217
|
+
|
|
218
|
+
// Store the handoff text for the session_switch event to pick up
|
|
219
|
+
// We use the parent session file as key since that's what we pass to newSession
|
|
220
|
+
if (currentSessionFile) {
|
|
221
|
+
pendingHandoffText.set(currentSessionFile, messageToSend);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Create new session immediately
|
|
225
|
+
// Use ctx.newSession if available (command mode), otherwise use sessionManager directly
|
|
226
|
+
if ("newSession" in ctx && typeof ctx.newSession === "function") {
|
|
227
|
+
const newSessionResult = await ctx.newSession({
|
|
228
|
+
parentSession: currentSessionFile,
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
if (newSessionResult.cancelled) {
|
|
232
|
+
// Clean up pending text if cancelled
|
|
233
|
+
if (currentSessionFile) {
|
|
234
|
+
pendingHandoffText.delete(currentSessionFile);
|
|
235
|
+
}
|
|
236
|
+
return "New session cancelled.";
|
|
237
|
+
}
|
|
238
|
+
} else {
|
|
239
|
+
// Tool/hook contexts: create session directly via session manager
|
|
240
|
+
const sessionManager = ctx.sessionManager as any;
|
|
241
|
+
sessionManager.newSession({ parentSession: currentSessionFile });
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
pi.setSessionName(goalToSessionName(goal));
|
|
245
|
+
|
|
246
|
+
return undefined;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
export default function (pi: ExtensionAPI) {
|
|
250
|
+
// --- Session switch handler ---
|
|
251
|
+
// When switching to a new session (e.g., after handoff), check if there's
|
|
252
|
+
// pending handoff text to set in the editor.
|
|
253
|
+
pi.on("session_switch", async (event, ctx) => {
|
|
254
|
+
if (event.reason !== "new" || !ctx.hasUI) return;
|
|
255
|
+
|
|
256
|
+
// Get the parent session from the session header
|
|
257
|
+
const header = ctx.sessionManager.getHeader();
|
|
258
|
+
const parentSession = header?.parentSession;
|
|
259
|
+
if (!parentSession) return;
|
|
260
|
+
|
|
261
|
+
// Check if there's pending handoff text for this parent session
|
|
262
|
+
const text = pendingHandoffText.get(parentSession);
|
|
263
|
+
if (text) {
|
|
264
|
+
ctx.ui.setEditorText(text);
|
|
265
|
+
ctx.ui.notify("Handoff ready - edit if needed and press Enter to send", "info");
|
|
266
|
+
pendingHandoffText.delete(parentSession);
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
// --- System prompt hint ---
|
|
271
|
+
// Inject handoff awareness into the system prompt so the model
|
|
272
|
+
// can proactively suggest handoffs at high context usage.
|
|
273
|
+
pi.on("before_agent_start", async (event, _ctx) => {
|
|
274
|
+
return {
|
|
275
|
+
systemPrompt: event.systemPrompt + HANDOFF_SYSTEM_HINT,
|
|
276
|
+
};
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
// --- Auto-handoff on compaction ---
|
|
280
|
+
// When auto-compaction triggers, offer handoff as an alternative.
|
|
281
|
+
// Uses event.preparation (messagesToSummarize, previousSummary) — the
|
|
282
|
+
// manageable subset Pi already prepared — instead of re-gathering the
|
|
283
|
+
// full conversation that caused the compaction in the first place.
|
|
284
|
+
pi.on("session_before_compact", async (event, ctx) => {
|
|
285
|
+
if (!ctx.hasUI || !ctx.model) return;
|
|
286
|
+
|
|
287
|
+
// Skip if a handoff was just initiated - the new session is already being created
|
|
288
|
+
const currentSessionFile = ctx.sessionManager.getSessionFile();
|
|
289
|
+
if (currentSessionFile && pendingHandoffText.has(currentSessionFile)) {
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const usage = ctx.getContextUsage();
|
|
294
|
+
const pctStr = usage ? `${Math.round(usage.percent)}%` : "high";
|
|
295
|
+
|
|
296
|
+
const choice = await ctx.ui.select(
|
|
297
|
+
`Context is ${pctStr} full. What would you like to do?`,
|
|
298
|
+
["Handoff to new session", "Compact context", "Continue without either"],
|
|
299
|
+
);
|
|
300
|
+
|
|
301
|
+
if (choice === "Compact context" || choice === undefined) return;
|
|
302
|
+
if (choice === "Continue without either") return { cancel: true };
|
|
303
|
+
|
|
304
|
+
// Build context from preparation data — already the right subset
|
|
305
|
+
const { preparation } = event;
|
|
306
|
+
const conversationText = serializeConversation(
|
|
307
|
+
convertToLlm(preparation.messagesToSummarize),
|
|
308
|
+
);
|
|
309
|
+
|
|
310
|
+
let contextForHandoff = "";
|
|
311
|
+
if (preparation.previousSummary) {
|
|
312
|
+
contextForHandoff += `## Previous Context\n\n${preparation.previousSummary}\n\n`;
|
|
313
|
+
}
|
|
314
|
+
contextForHandoff += `## Recent Conversation\n\n${conversationText}`;
|
|
315
|
+
|
|
316
|
+
const error = await performHandoff(pi, ctx, "Continue current work", "compactHook", contextForHandoff);
|
|
317
|
+
if (error) {
|
|
318
|
+
ctx.ui.notify(`Handoff failed: ${error}. Compacting instead.`, "warning");
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
return { cancel: true };
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
// --- /handoff command ---
|
|
326
|
+
pi.registerCommand("handoff", {
|
|
327
|
+
description: "Transfer context to a new focused session",
|
|
328
|
+
handler: async (args, ctx) => {
|
|
329
|
+
const goal = args.trim();
|
|
330
|
+
if (!goal) {
|
|
331
|
+
ctx.ui.notify("Usage: /handoff <goal for new thread>", "error");
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
const error = await performHandoff(pi, ctx, goal);
|
|
336
|
+
if (error) {
|
|
337
|
+
ctx.ui.notify(error, "error");
|
|
338
|
+
}
|
|
339
|
+
},
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
// --- handoff tool (agent-callable) ---
|
|
343
|
+
pi.registerTool({
|
|
344
|
+
name: "handoff",
|
|
345
|
+
label: "Handoff",
|
|
346
|
+
description:
|
|
347
|
+
"Transfer context to a new focused session. ONLY use this when the user explicitly asks for a handoff. Provide a goal describing what the new session should focus on.",
|
|
348
|
+
parameters: Type.Object({
|
|
349
|
+
goal: Type.String({ description: "The goal/task for the new session" }),
|
|
350
|
+
}),
|
|
351
|
+
|
|
352
|
+
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
353
|
+
const error = await performHandoff(pi, ctx, params.goal, "tool");
|
|
354
|
+
return {
|
|
355
|
+
content: [
|
|
356
|
+
{
|
|
357
|
+
type: "text" as const,
|
|
358
|
+
text: error ?? "Handoff queued. Switching to a new session with the generated prompt.",
|
|
359
|
+
},
|
|
360
|
+
],
|
|
361
|
+
};
|
|
362
|
+
},
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
}
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session Query Extension - Query previous pi sessions
|
|
3
|
+
*
|
|
4
|
+
* Provides a tool the model can use to query past sessions for context,
|
|
5
|
+
* decisions, code changes, or other information.
|
|
6
|
+
*
|
|
7
|
+
* Works with handoff: when a handoff prompt includes "Parent session: <path>",
|
|
8
|
+
* the model can use this tool to look up details from that session.
|
|
9
|
+
*
|
|
10
|
+
* Based on pi-amplike's session-query, enhanced with:
|
|
11
|
+
* - Better error handling
|
|
12
|
+
* - Rendered results with markdown support
|
|
13
|
+
* - Session metadata in response
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { complete, type Message } from "@mariozechner/pi-ai";
|
|
17
|
+
import type { ExtensionAPI, SessionEntry } from "@mariozechner/pi-coding-agent";
|
|
18
|
+
import {
|
|
19
|
+
SessionManager,
|
|
20
|
+
convertToLlm,
|
|
21
|
+
serializeConversation,
|
|
22
|
+
getMarkdownTheme,
|
|
23
|
+
} from "@mariozechner/pi-coding-agent";
|
|
24
|
+
import { Container, Markdown, Spacer, Text } from "@mariozechner/pi-tui";
|
|
25
|
+
import { Type } from "@sinclair/typebox";
|
|
26
|
+
import * as fs from "node:fs";
|
|
27
|
+
|
|
28
|
+
// Maximum characters of serialized conversation to send to the query LLM.
|
|
29
|
+
// Prevents blowing context when the parent session is very large.
|
|
30
|
+
// ~100k chars ≈ ~25-30k tokens for most models — leaves room for the
|
|
31
|
+
// question, system prompt, and answer within a 128k context window.
|
|
32
|
+
const MAX_SESSION_CHARS = 100_000;
|
|
33
|
+
|
|
34
|
+
const QUERY_SYSTEM_PROMPT = `Extract information relevant to the question from the session history.
|
|
35
|
+
Return a concise answer using bullet points where appropriate.
|
|
36
|
+
Use code pointers (path/to/file.ts:42 or path/to/file.ts#functionName) when referencing specific code.
|
|
37
|
+
If the information is not in the session, say so clearly.`;
|
|
38
|
+
|
|
39
|
+
export default function (pi: ExtensionAPI) {
|
|
40
|
+
pi.registerTool({
|
|
41
|
+
name: "session_query",
|
|
42
|
+
label: (params) => `Query Session: ${params.question}`,
|
|
43
|
+
description:
|
|
44
|
+
"Query a previous pi session file for context, decisions, or information. Use when you need to look up what happened in a parent session or any other session. The sessionPath should be the full path to a .jsonl session file.",
|
|
45
|
+
|
|
46
|
+
parameters: Type.Object({
|
|
47
|
+
sessionPath: Type.String({
|
|
48
|
+
description:
|
|
49
|
+
"Full path to the session file (e.g., /home/user/.pi/agent/sessions/.../session.jsonl)",
|
|
50
|
+
}),
|
|
51
|
+
question: Type.String({
|
|
52
|
+
description:
|
|
53
|
+
"What you want to know about that session (e.g., 'What files were modified?' or 'What approach was chosen?')",
|
|
54
|
+
}),
|
|
55
|
+
}),
|
|
56
|
+
|
|
57
|
+
renderResult(result, _options, theme) {
|
|
58
|
+
const container = new Container();
|
|
59
|
+
|
|
60
|
+
if (result.content && result.content[0]?.text) {
|
|
61
|
+
const text = result.content[0].text;
|
|
62
|
+
|
|
63
|
+
// Check for error format
|
|
64
|
+
if (result.details?.error) {
|
|
65
|
+
container.addChild(new Text(theme.fg("error", text), 0, 0));
|
|
66
|
+
return container;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Parse structured response: **Query:** question\n\n---\n\nanswer
|
|
70
|
+
const match = text.match(/\*\*Query:\*\* (.+?)\n\n---\n\n([\s\S]+)/);
|
|
71
|
+
|
|
72
|
+
if (match) {
|
|
73
|
+
const [, query, answer] = match;
|
|
74
|
+
container.addChild(new Text(theme.bold("Query: ") + theme.fg("accent", query), 0, 0));
|
|
75
|
+
container.addChild(new Spacer(1));
|
|
76
|
+
// Render the answer as markdown
|
|
77
|
+
container.addChild(
|
|
78
|
+
new Markdown(answer.trim(), 0, 0, getMarkdownTheme(), {
|
|
79
|
+
color: (text: string) => theme.fg("toolOutput", text),
|
|
80
|
+
}),
|
|
81
|
+
);
|
|
82
|
+
} else {
|
|
83
|
+
// Fallback for other formats
|
|
84
|
+
container.addChild(new Text(theme.fg("toolOutput", text), 0, 0));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Show metadata if available
|
|
88
|
+
if (result.details?.messageCount) {
|
|
89
|
+
const truncNote = result.details.truncated ? ", truncated" : "";
|
|
90
|
+
container.addChild(new Spacer(1));
|
|
91
|
+
container.addChild(
|
|
92
|
+
new Text(
|
|
93
|
+
theme.fg("dim", `(${result.details.messageCount} messages in session${truncNote})`),
|
|
94
|
+
0,
|
|
95
|
+
0,
|
|
96
|
+
),
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return container;
|
|
102
|
+
},
|
|
103
|
+
|
|
104
|
+
async execute(toolCallId, params, signal, onUpdate, ctx) {
|
|
105
|
+
const { sessionPath, question } = params;
|
|
106
|
+
|
|
107
|
+
// Helper for error returns
|
|
108
|
+
const errorResult = (text: string) => ({
|
|
109
|
+
content: [{ type: "text" as const, text }],
|
|
110
|
+
details: { error: true },
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
// Validate session path
|
|
114
|
+
if (!sessionPath.endsWith(".jsonl")) {
|
|
115
|
+
return errorResult(
|
|
116
|
+
`Error: Invalid session path. Expected a .jsonl file, got: ${sessionPath}`,
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Check if file exists
|
|
121
|
+
if (!fs.existsSync(sessionPath)) {
|
|
122
|
+
return errorResult(`Error: Session file not found: ${sessionPath}`);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
onUpdate?.({
|
|
126
|
+
content: [
|
|
127
|
+
{
|
|
128
|
+
type: "text",
|
|
129
|
+
text: `Querying session: ${question}`,
|
|
130
|
+
},
|
|
131
|
+
],
|
|
132
|
+
details: { status: "loading", question },
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
// Load the session
|
|
136
|
+
let sessionManager: SessionManager;
|
|
137
|
+
try {
|
|
138
|
+
sessionManager = SessionManager.open(sessionPath);
|
|
139
|
+
} catch (err) {
|
|
140
|
+
return errorResult(`Error loading session: ${err}`);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Get conversation from the session
|
|
144
|
+
const branch = sessionManager.getBranch();
|
|
145
|
+
const messages = branch
|
|
146
|
+
.filter((entry): entry is SessionEntry & { type: "message" } => entry.type === "message")
|
|
147
|
+
.map((entry) => entry.message);
|
|
148
|
+
|
|
149
|
+
if (messages.length === 0) {
|
|
150
|
+
return {
|
|
151
|
+
content: [{ type: "text" as const, text: "Session is empty - no messages found." }],
|
|
152
|
+
details: { empty: true },
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Serialize the conversation, truncating if too large
|
|
157
|
+
const llmMessages = convertToLlm(messages);
|
|
158
|
+
let conversationText = serializeConversation(llmMessages);
|
|
159
|
+
let truncated = false;
|
|
160
|
+
|
|
161
|
+
if (conversationText.length > MAX_SESSION_CHARS) {
|
|
162
|
+
// Keep the tail (most recent context) — more likely to be relevant
|
|
163
|
+
conversationText = "…[earlier messages truncated]…\n\n"
|
|
164
|
+
+ conversationText.slice(-MAX_SESSION_CHARS);
|
|
165
|
+
truncated = true;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Use LLM to answer the question
|
|
169
|
+
if (!ctx.model) {
|
|
170
|
+
return errorResult("Error: No model available to analyze the session.");
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
try {
|
|
174
|
+
const apiKey = await ctx.modelRegistry.getApiKey(ctx.model);
|
|
175
|
+
|
|
176
|
+
const userMessage: Message = {
|
|
177
|
+
role: "user",
|
|
178
|
+
content: [
|
|
179
|
+
{
|
|
180
|
+
type: "text",
|
|
181
|
+
text: `## Session Conversation\n\n${conversationText}\n\n## Question\n\n${question}`,
|
|
182
|
+
},
|
|
183
|
+
],
|
|
184
|
+
timestamp: Date.now(),
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
const response = await complete(
|
|
188
|
+
ctx.model,
|
|
189
|
+
{ systemPrompt: QUERY_SYSTEM_PROMPT, messages: [userMessage] },
|
|
190
|
+
{ apiKey, signal },
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
if (response.stopReason === "aborted") {
|
|
194
|
+
return {
|
|
195
|
+
content: [{ type: "text" as const, text: "Query was cancelled." }],
|
|
196
|
+
details: { cancelled: true },
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const answer = response.content
|
|
201
|
+
.filter((c): c is { type: "text"; text: string } => c.type === "text")
|
|
202
|
+
.map((c) => c.text)
|
|
203
|
+
.join("\n");
|
|
204
|
+
|
|
205
|
+
return {
|
|
206
|
+
content: [{ type: "text" as const, text: `**Query:** ${question}\n\n---\n\n${answer}` }],
|
|
207
|
+
details: {
|
|
208
|
+
sessionPath,
|
|
209
|
+
question,
|
|
210
|
+
messageCount: messages.length,
|
|
211
|
+
truncated,
|
|
212
|
+
},
|
|
213
|
+
};
|
|
214
|
+
} catch (err) {
|
|
215
|
+
return errorResult(`Error querying session: ${err}`);
|
|
216
|
+
}
|
|
217
|
+
},
|
|
218
|
+
});
|
|
219
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ssweens/pi-handoff",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Enhanced handoff extension for pi - context management for agentic coding workflows",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"pi-package"
|
|
7
|
+
],
|
|
8
|
+
"type": "module",
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"author": "ssweens",
|
|
11
|
+
"files": [
|
|
12
|
+
"extensions/",
|
|
13
|
+
"skills/",
|
|
14
|
+
"README.md",
|
|
15
|
+
"LICENSE",
|
|
16
|
+
"screenshot.png"
|
|
17
|
+
],
|
|
18
|
+
"pi": {
|
|
19
|
+
"extensions": [
|
|
20
|
+
"extensions"
|
|
21
|
+
],
|
|
22
|
+
"skills": [
|
|
23
|
+
"skills"
|
|
24
|
+
]
|
|
25
|
+
},
|
|
26
|
+
"peerDependencies": {
|
|
27
|
+
"@mariozechner/pi-ai": "*",
|
|
28
|
+
"@mariozechner/pi-coding-agent": "*",
|
|
29
|
+
"@mariozechner/pi-tui": "*",
|
|
30
|
+
"@sinclair/typebox": "*"
|
|
31
|
+
}
|
|
32
|
+
}
|
package/screenshot.png
ADDED
|
Binary file
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: pi-session-query
|
|
3
|
+
description: Query previous pi sessions to retrieve context, decisions, code changes, or other information. Use when you need to look up what happened in a parent session or any other session file.
|
|
4
|
+
disable-model-invocation: true
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Pi Session Query
|
|
8
|
+
|
|
9
|
+
Query pi session files to retrieve context from past conversations.
|
|
10
|
+
|
|
11
|
+
This skill is automatically invoked in handed-off sessions when you need to look up details from the parent session.
|
|
12
|
+
|
|
13
|
+
## When to Use
|
|
14
|
+
|
|
15
|
+
- When the handoff summary references a "Parent session" path
|
|
16
|
+
- When you need specific details not included in the handoff summary
|
|
17
|
+
- When you need to verify a decision or approach from the parent session
|
|
18
|
+
- When you need file paths or code snippets from earlier work
|
|
19
|
+
|
|
20
|
+
## Usage
|
|
21
|
+
|
|
22
|
+
Use the `session_query` tool:
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
session_query(sessionPath, question)
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
**Parameters:**
|
|
29
|
+
- `sessionPath`: Full path to the session file (provided in the "Parent session:" line)
|
|
30
|
+
- `question`: Specific question about that session
|
|
31
|
+
|
|
32
|
+
## Examples
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
// Find what files were changed
|
|
36
|
+
session_query("/path/to/session.jsonl", "What files were modified?")
|
|
37
|
+
|
|
38
|
+
// Get approach details
|
|
39
|
+
session_query("/path/to/session.jsonl", "What approach was chosen for authentication?")
|
|
40
|
+
|
|
41
|
+
// Get specific code decisions
|
|
42
|
+
session_query("/path/to/session.jsonl", "What error handling pattern was used?")
|
|
43
|
+
|
|
44
|
+
// Summarize key decisions
|
|
45
|
+
session_query("/path/to/session.jsonl", "Summarize the key decisions made")
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Best Practices
|
|
49
|
+
|
|
50
|
+
1. **Be specific** - Ask targeted questions for better results
|
|
51
|
+
2. **Reference code** - Ask about specific files or functions when relevant
|
|
52
|
+
3. **Verify before assuming** - If the handoff summary seems incomplete, query for details
|
|
53
|
+
4. **Don't over-query** - The handoff summary should have most context; query only when needed
|
|
54
|
+
|
|
55
|
+
## How It Works
|
|
56
|
+
|
|
57
|
+
The tool loads the referenced session file, extracts the conversation history, and uses the LLM to answer your question based on its contents. This allows context retrieval without loading the full parent session into your context window.
|