Remove stale add-compact skill, improve memory handoff with group-aware search
- Delete .agent/skills/add-compact/ (superseded by real implementation) - Remove from library.yaml and ASPIRATIONAL_SKILLS - compactSession now tags memories with groupFolder for targeted recall - Memory handoff uses searchMemories(group) before falling back to getImportantMemories Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
ef0baf9e95
commit
7d06df9bf9
5 changed files with 31 additions and 112 deletions
|
|
@ -1,97 +0,0 @@
|
|||
---
|
||||
name: add-compact
|
||||
description: Add /compact command for manual session reset with context summary. Reduces context bloat in long pi sessions by summarizing the conversation and starting a fresh pi session, preserving key context as a handoff prompt.
|
||||
---
|
||||
|
||||
# Add /compact Command
|
||||
|
||||
Adds a `/compact` session command that fights context rot in long-running sessions. Unlike NanoClaw's Claude-SDK-based compaction, Clawdie uses pi as the agent runtime — which has no built-in `/compact`. Instead, we implement it by:
|
||||
|
||||
1. Asking pi to summarize the current session as a structured handoff
|
||||
2. Starting a fresh pi session (`--no-session`) with the summary injected as context
|
||||
3. Returning the new session ID to the orchestrator
|
||||
|
||||
**Main-group or trusted sender only.**
|
||||
|
||||
## Phase 1: Pre-flight
|
||||
|
||||
Check if `src/session-commands.ts` exists:
|
||||
|
||||
```bash
|
||||
test -f src/session-commands.ts && echo "Already applied" || echo "Not applied"
|
||||
```
|
||||
|
||||
If already applied, skip to Phase 3 (Verify).
|
||||
|
||||
## Phase 2: Apply Code Changes
|
||||
|
||||
### 1. Create `src/session-commands.ts`
|
||||
|
||||
Handles detection and authorization of slash commands in incoming messages:
|
||||
|
||||
```typescript
|
||||
// Detect /compact in message text
|
||||
// Returns true only for main-group or is_from_me (admin) senders
|
||||
export function isCompactCommand(text: string): boolean { ... }
|
||||
export function isAuthorizedForSessionCommands(isMain: boolean, isFromMe: boolean): boolean { ... }
|
||||
```
|
||||
|
||||
### 2. Create `src/session-commands.test.ts`
|
||||
|
||||
Unit tests for command parsing and auth logic.
|
||||
|
||||
### 3. Intercept in `src/index.ts`
|
||||
|
||||
In `processGroupMessages`, before passing the prompt to `runJailAgent`:
|
||||
|
||||
```typescript
|
||||
if (isCompactCommand(messageText) && isAuthorizedForSessionCommands(isMain, isFromMe)) {
|
||||
// 1. Ask pi to summarize the current session
|
||||
const summaryOutput = await runJailAgent(group, {
|
||||
prompt: 'Summarize our conversation so far in 2-3 paragraphs covering key context, decisions made, and any open tasks. Be concise.',
|
||||
sessionId: currentSessionId,
|
||||
groupFolder, chatJid, isMain,
|
||||
}, onProcess);
|
||||
|
||||
// 2. Start fresh session with summary as context
|
||||
const compactOutput = await runJailAgent(group, {
|
||||
prompt: `[Compacted context from previous session]\n\n${summaryOutput.result}\n\n[New session starts here. Continue from this context.]`,
|
||||
sessionId: undefined, // fresh session
|
||||
groupFolder, chatJid, isMain,
|
||||
}, onProcess);
|
||||
|
||||
// 3. Send acknowledgement
|
||||
await sendMessage(chatJid, 'Session compacted. Continuing with summarized context.');
|
||||
return;
|
||||
}
|
||||
```
|
||||
|
||||
### Validate
|
||||
|
||||
```bash
|
||||
npm test
|
||||
npm run build
|
||||
```
|
||||
|
||||
## Phase 3: Verify
|
||||
|
||||
1. Start Clawdie: `sudo service clawdie start`
|
||||
2. Have a multi-turn conversation in the **main group** to build up context.
|
||||
3. Send: `/compact`
|
||||
4. Verify:
|
||||
- Bot acknowledges compaction
|
||||
- Session continues with summarized context
|
||||
- `groups/{folder}/sessions/` shows a new `.jsonl` session file
|
||||
5. From a **non-main group** as non-admin, send: `@<assistant> /compact`
|
||||
6. Verify: bot responds "Session commands require admin access."
|
||||
|
||||
## Security Constraints
|
||||
|
||||
- **Main-group or admin only.** Non-main groups are untrusted.
|
||||
- **No auto-compaction.** Manual only.
|
||||
- **Summary step uses the existing session** — no data loss before reset.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- **Summary is empty:** Pi may have hit an error or token limit. Check `groups/{folder}/logs/agent-*.log`.
|
||||
- **New session not created:** Verify `PI_TUI_NO_SESSION` is not set in `.env`.
|
||||
|
|
@ -262,11 +262,6 @@ features:
|
|||
tags: [channel, voice, ml]
|
||||
manifest: manifest.yaml
|
||||
|
||||
- id: feature/add-compact
|
||||
source: local:.agent/skills/add-compact/
|
||||
description: Add /compact command for manual session reset with context summary handoff
|
||||
tags: [agent, memory]
|
||||
|
||||
- id: feature/add-parallel
|
||||
source: local:.agent/skills/add-parallel/
|
||||
description: Parallel AI MCP integration for multi-step web research
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ import { resolveGroupFolderPath, resolveGroupIpcPath } from './group-folder.js';
|
|||
import { markJailRunFinished, markJailRunStarted } from './health.js';
|
||||
import { incCounter, incLabeledCounter, registerGauge } from './metrics.js';
|
||||
import { compactSession } from './session-compaction.js';
|
||||
import { getImportantMemories } from './memory-pg.js';
|
||||
import { getImportantMemories, searchMemories } from './memory-pg.js';
|
||||
|
||||
const METRICS_PREFIX = `${AGENT_NAME}_`;
|
||||
import { logger } from './logger.js';
|
||||
|
|
@ -256,7 +256,9 @@ export async function runJailAgent(
|
|||
if (oversize && input.sessionId) {
|
||||
const sessionFile = path.join(sessionDir, input.sessionId);
|
||||
try {
|
||||
const compactResult = await compactSession(sessionFile);
|
||||
const compactResult = await compactSession(sessionFile, {
|
||||
groupFolder: input.groupFolder,
|
||||
});
|
||||
if (compactResult.compacted) {
|
||||
logger.info(
|
||||
{
|
||||
|
|
@ -310,15 +312,32 @@ export async function runJailAgent(
|
|||
// so the agent doesn't amnesia completely.
|
||||
if (needsMemoryHandoff) {
|
||||
try {
|
||||
const memories = await getImportantMemories(5);
|
||||
if (memories.length > 0) {
|
||||
const handoffLines = memories.map((m) => {
|
||||
const summary =
|
||||
// Try group-aware search first (finds compaction summaries tagged with this group),
|
||||
// fall back to generic important memories if search returns nothing.
|
||||
const searchResults = await searchMemories(
|
||||
`session-compaction ${input.groupFolder}`,
|
||||
5,
|
||||
).catch(() => []);
|
||||
const handoffLines: string[] = [];
|
||||
if (searchResults.length > 0) {
|
||||
for (const r of searchResults) {
|
||||
const text =
|
||||
r.summary.length > 300
|
||||
? r.summary.slice(0, 300) + '...'
|
||||
: r.summary;
|
||||
handoffLines.push(`- ${text}`);
|
||||
}
|
||||
} else {
|
||||
const memories = await getImportantMemories(5);
|
||||
for (const m of memories) {
|
||||
const text =
|
||||
m.summary.length > 300
|
||||
? m.summary.slice(0, 300) + '...'
|
||||
: m.summary;
|
||||
return `- ${summary}`;
|
||||
});
|
||||
handoffLines.push(`- ${text}`);
|
||||
}
|
||||
}
|
||||
if (handoffLines.length > 0) {
|
||||
const handoff = [
|
||||
'<session-reset-context>',
|
||||
'Previous session was reset. Key context from memory:',
|
||||
|
|
|
|||
|
|
@ -217,6 +217,7 @@ export async function compactSession(
|
|||
keepTurns?: number;
|
||||
minEntries?: number;
|
||||
enabled?: boolean;
|
||||
groupFolder?: string;
|
||||
} = {},
|
||||
): Promise<CompactionResult> {
|
||||
const {
|
||||
|
|
@ -289,8 +290,10 @@ export async function compactSession(
|
|||
|
||||
let memoryStored = false;
|
||||
try {
|
||||
const topics = ['session-compaction'];
|
||||
if (options.groupFolder) topics.push(options.groupFolder);
|
||||
await storeMemory(summary, {
|
||||
topics: ['session-compaction'],
|
||||
topics,
|
||||
importance: 4,
|
||||
keyFacts: old
|
||||
.filter((e) => e.result === 'success' && e.task)
|
||||
|
|
|
|||
|
|
@ -108,7 +108,6 @@ function extractRepoPaths(content: string): string[] {
|
|||
* files the skill will create when applied.
|
||||
*/
|
||||
const ASPIRATIONAL_SKILLS = new Set([
|
||||
'add-compact',
|
||||
'add-discord',
|
||||
'add-gmail',
|
||||
'add-parallel',
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue