@shanepadgett/tau-agent 0.30.0 → 0.31.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.
@@ -36,7 +36,7 @@ Adds `/context` to set branch-local reusable repository work scopes from `.pi/co
36
36
 
37
37
  ## working-memory
38
38
 
39
- Gives agent `working_memory` for selective hard checkpoints. Model-only references identify useful conversation evidence and complete tool exchanges. Requested source files return as structural outlines, while deferred files remain cheap conditional reminders. Everything else before checkpoint leaves future model input without changing saved session. Advisory reminders begin at 40k active-context tokens. Run `/prune` to request reassessment manually.
39
+ Gives agent `working_memory` for selective hard checkpoints. Model-only references identify useful user messages and visible assistant text. Requested source files return as structural outlines, deferred files remain cheap conditional reminders, and a continuation note carries conclusions extracted from exploration. Tool history and full file reads leave future model input without changing saved session. Advisory reminders begin at 40k active-context tokens. Run `/prune` to request reassessment manually.
40
40
 
41
41
  ## explore
42
42
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  Working Memory gives agent selective checkpoints without changing saved conversation.
4
4
 
5
- `working_memory` keeps referenced conversation evidence and complete tool exchanges, carries requested source files as structural outlines, and records deferred files as conditional reminders. Everything else before checkpoint leaves future model context.
5
+ `working_memory` keeps referenced user messages and visible assistant text, carries requested source files as structural outlines, and records deferred files as conditional reminders. Tool history and full file reads leave model context; useful findings from them are distilled into a compact continuation note.
6
6
 
7
7
  Automatic reminders begin at 40,000 active-context tokens and remain advisory. Run `/prune` to request reassessment manually.
8
8
 
@@ -3,7 +3,7 @@ import { type ExtensionAPI, type ExtensionContext, type SessionEntry } from "@ea
3
3
  import { type Static, Type } from "typebox";
4
4
  import { truncateBoundedHead } from "../../shared/bounded-text-result.ts";
5
5
  import { requestOutlineInjections, type PreparedOutlineInjection } from "../../shared/outline-injection.ts";
6
- import { buildMemoryCatalog } from "./memory.ts";
6
+ import { buildMemoryCatalog, collectPrunedRowIds } from "./memory.ts";
7
7
  import { WORKING_MEMORY_TOOL, type DeferredFile, type WorkingMemoryCheckpointDetailsV1 } from "./state.ts";
8
8
 
9
9
  const PATH = Type.String({ minLength: 1, maxLength: 500, pattern: "\\S" });
@@ -58,7 +58,7 @@ export async function executeWorkingMemory(options: ExecuteWorkingMemoryOptions)
58
58
  const unit = catalog.get(ref);
59
59
  return unit ? [unit] : [];
60
60
  })
61
- .sort((left, right) => left.order - right.order || left.suborder - right.suborder);
61
+ .sort((left, right) => left.order - right.order);
62
62
  const retainedRefs = retained.map((unit) => unit.ref);
63
63
  const warnings = requestedRefs
64
64
  .filter((ref) => !catalog.has(ref))
@@ -89,9 +89,8 @@ export async function executeWorkingMemory(options: ExecuteWorkingMemoryOptions)
89
89
  }
90
90
 
91
91
  const anchorIndex = findAnchorEntry(branch, options.toolCallId);
92
- const retainedSet = new Set(retainedRefs);
93
92
  const preAnchorUnits = [...catalog.values()].filter((unit) => unit.order < anchorIndex);
94
- const prunedRowIds = [...new Set(preAnchorUnits.flatMap((unit) => (retainedSet.has(unit.ref) ? [] : unit.rowIds)))];
93
+ const prunedRowIds = collectPrunedRowIds(branch, anchorIndex);
95
94
  const details: WorkingMemoryCheckpointDetailsV1 = {
96
95
  v: 1,
97
96
  anchorToolCallId: options.toolCallId,
@@ -22,7 +22,7 @@ import { replayWorkingMemoryState, WORKING_MEMORY_TOOL, type WorkingMemoryCheckp
22
22
  const NUDGE_TYPE = "tau.working-memory.nudge";
23
23
  const BASELINE_TYPE = "tau.working-memory.nudge-baseline";
24
24
  const TOOL_DESCRIPTION =
25
- "Create a selective hard checkpoint for future model context. Keep valuable referenced evidence and complete tool exchanges, carry file structure as outlines, defer conditionally relevant files without reading them, and provide one compact continuation note. Never checkpoint useful evidence merely to reread it.";
25
+ "Create a selective hard checkpoint for future model context. Keep valuable user and visible assistant messages, carry file structure as outlines, defer conditionally relevant files without reading them, and distill exploration findings into one compact continuation note.";
26
26
 
27
27
  interface NudgeState {
28
28
  anchorToolCallId: string | undefined;
@@ -98,9 +98,9 @@ export default function workingMemoryExtension(pi: ExtensionAPI): void {
98
98
  promptSnippet: "Reassess and selectively checkpoint active working memory",
99
99
  promptGuidelines: [
100
100
  "Use working_memory when stale evidence has accumulated or a memory reminder asks for reassessment; continue coherent exploration when current evidence remains useful.",
101
- "A hidden working-memory reference catalog provides keep refs. Message refs preserve conversational evidence; tool refs preserve one complete call/result exchange.",
102
- "Keep active or expensive evidence, outline files when structure suffices, defer paths whose relevance is conditional, and discard evidence with no expected value.",
103
- "Everything before working_memory leaves future model context unless selected in keep. Put durable decisions, constraints, unresolved matters, and next action in continuation without duplicating retained evidence.",
101
+ "A hidden working-memory reference catalog provides keep refs only for user messages and visible assistant text. Tool calls, tool results, hidden reasoning, and framework messages cannot be retained.",
102
+ "Outline files that remain in the repository working set, defer paths whose relevance is conditional, and discard exploration history after extracting its useful conclusions.",
103
+ "Everything before working_memory leaves future model context unless selected in keep. Put findings from tools, durable decisions, constraints, unresolved matters, and next action in continuation without duplicating retained messages.",
104
104
  ],
105
105
  parameters: workingMemoryParameters,
106
106
  executionMode: "sequential",
@@ -9,23 +9,13 @@ export interface MemoryUnit {
9
9
  label: string;
10
10
  preview: string;
11
11
  order: number;
12
- suborder: number;
13
12
  messages: ContextMessage[];
14
- rowIds: string[];
15
13
  }
16
14
 
17
- const EXCLUDED_CUSTOM_TYPES = new Set(["tau.autoread", "tau.runtime-context", "tau.working-memory.nudge"]);
18
15
  const REFERENCE_CATALOG_TYPE = "tau.working-memory.references";
19
16
 
20
17
  export function buildMemoryCatalog(branch: readonly SessionEntry[]): Map<string, MemoryUnit> {
21
18
  const catalog = new Map<string, MemoryUnit>();
22
- const results = new Map<string, ContextMessage>();
23
- for (const entry of branch) {
24
- for (const message of sessionEntryToContextMessages(entry)) {
25
- if (message.role === "toolResult") results.set(message.toolCallId, message);
26
- }
27
- }
28
-
29
19
  for (let order = 0; order < branch.length; order += 1) {
30
20
  const entry = branch[order];
31
21
  if (!entry) continue;
@@ -35,61 +25,56 @@ export function buildMemoryCatalog(branch: readonly SessionEntry[]): Map<string,
35
25
  const calls = message.content.filter((block) => block.type === "toolCall");
36
26
  const frameworkCall = calls.some((call) => call.name === "working_memory");
37
27
  if (frameworkCall) continue;
38
- const prose = message.content.filter((block) => block.type !== "toolCall");
39
- if (prose.length > 0) {
28
+ const text = message.content.filter((block) => block.type === "text");
29
+ if (text.length > 0) {
40
30
  const ref = `m:${entry.id}`;
41
31
  catalog.set(ref, {
42
32
  ref,
43
33
  label: "assistant",
44
- preview: previewAssistant(prose),
34
+ preview: previewAssistant(text),
45
35
  order,
46
- suborder: 0,
47
- messages: [{ ...message, content: prose }],
48
- rowIds: [],
49
- });
50
- }
51
- for (let index = 0; index < calls.length; index += 1) {
52
- const call = calls[index];
53
- if (!call) continue;
54
- const result = results.get(call.id);
55
- if (result?.role !== "toolResult" || result.toolName !== call.name) continue;
56
- const ref = `t:${entry.id}:${index + 1}`;
57
- catalog.set(ref, {
58
- ref,
59
- label: `tool ${call.name}`,
60
- preview: previewTool(call.arguments),
61
- order,
62
- suborder: index + 1,
63
- messages: [{ ...message, content: [call] }, result],
64
- rowIds: [call.id],
36
+ messages: [{ ...message, content: text }],
65
37
  });
66
38
  }
67
39
  continue;
68
40
  }
69
- if (message.role === "toolResult") continue;
70
- if (message.role === "custom" && EXCLUDED_CUSTOM_TYPES.has(message.customType)) continue;
71
- if (
72
- message.role === "custom" &&
73
- message.customType === "tau.injected-context" &&
74
- message.details &&
75
- typeof message.details === "object" &&
76
- (message.details as Record<string, unknown>).source === "context"
77
- )
78
- continue;
41
+ if (message.role !== "user") continue;
79
42
  const ref = `m:${entry.id}`;
80
43
  catalog.set(ref, {
81
44
  ref,
82
- label: message.role === "custom" ? message.customType : message.role,
83
- preview: previewMessage(message),
45
+ label: "user",
46
+ preview: previewUser(message),
84
47
  order,
85
- suborder: 0,
86
48
  messages: [message],
87
- rowIds: outlineRowIds(message),
88
49
  });
89
50
  }
90
51
  return catalog;
91
52
  }
92
53
 
54
+ export function collectPrunedRowIds(branch: readonly SessionEntry[], before: number): string[] {
55
+ const rowIds = new Set<string>();
56
+ for (let index = 0; index < before; index += 1) {
57
+ const entry = branch[index];
58
+ if (!entry) continue;
59
+ for (const message of sessionEntryToContextMessages(entry)) {
60
+ if (message.role === "assistant") {
61
+ for (const block of message.content) {
62
+ if (block.type === "toolCall" && block.name !== "working_memory") rowIds.add(block.id);
63
+ }
64
+ }
65
+ if (
66
+ message.role === "custom" &&
67
+ message.customType === "tau.explore.outline" &&
68
+ isRecord(message.details) &&
69
+ typeof message.details.rowId === "string"
70
+ ) {
71
+ rowIds.add(message.details.rowId);
72
+ }
73
+ }
74
+ }
75
+ return [...rowIds];
76
+ }
77
+
93
78
  export function projectWorkingMemory(
94
79
  messages: readonly ContextMessage[],
95
80
  state: ActiveWorkingMemoryState,
@@ -109,7 +94,7 @@ export function projectWorkingMemory(
109
94
  const unit = catalog.get(ref);
110
95
  return unit ? [unit] : [];
111
96
  })
112
- .sort((left, right) => left.order - right.order || left.suborder - right.suborder);
97
+ .sort((left, right) => left.order - right.order);
113
98
  const projected: ContextMessage[] = [];
114
99
  const projectedRefs: MemoryUnit[][] = [];
115
100
  for (const unit of retained) {
@@ -137,12 +122,6 @@ function referenceCurrentMessages(
137
122
  (message) => !isContextProjectionMessage(message) && !isLegacyContextMessage(message),
138
123
  );
139
124
  if (generated.length !== sessionMessages.length) return messages.map(() => []);
140
- const toolRefs = new Map<string, MemoryUnit[]>();
141
- for (const unit of catalog.values()) {
142
- const result = unit.messages.find((message) => message.role === "toolResult");
143
- if (result?.role !== "toolResult") continue;
144
- toolRefs.set(result.toolCallId, [...(toolRefs.get(result.toolCallId) ?? []), unit]);
145
- }
146
125
  let generatedIndex = 0;
147
126
  return messages.map((currentMessage) => {
148
127
  if (isContextProjectionMessage(currentMessage) || isLegacyContextMessage(currentMessage)) return [];
@@ -150,13 +129,9 @@ function referenceCurrentMessages(
150
129
  generatedIndex += 1;
151
130
  if (!current) return [];
152
131
  const { entry, message } = current;
153
- if (message.role === "assistant") {
154
- const unit = catalog.get(`m:${entry.id}`);
155
- return unit && currentMessage.role === "assistant" ? [unit] : [];
156
- }
157
- if (message.role === "toolResult") return toolRefs.get(message.toolCallId) ?? [];
132
+ if (message.role !== "user" && message.role !== "assistant") return [];
158
133
  const unit = catalog.get(`m:${entry.id}`);
159
- return unit ? [unit] : [];
134
+ return unit && currentMessage.role === message.role ? [unit] : [];
160
135
  });
161
136
  }
162
137
 
@@ -184,7 +159,7 @@ function addReferenceCatalog(
184
159
  role: "custom",
185
160
  customType: REFERENCE_CATALOG_TYPE,
186
161
  content: [...unique.values()]
187
- .sort((left, right) => left.order - right.order || left.suborder - right.suborder)
162
+ .sort((left, right) => left.order - right.order)
188
163
  .map((unit) => `${unit.ref} (${unit.label}): ${unit.preview}`)
189
164
  .join("\n"),
190
165
  display: false,
@@ -224,36 +199,14 @@ function findAnchor(messages: readonly ContextMessage[], id: string): number {
224
199
  function previewAssistant(blocks: ReadonlyArray<{ type: string }>): string {
225
200
  for (const block of blocks) {
226
201
  if (block.type === "text" && "text" in block && typeof block.text === "string") return compact(block.text);
227
- if (block.type === "thinking" && "thinking" in block && typeof block.thinking === "string") {
228
- return compact(block.thinking);
229
- }
230
202
  }
231
203
  return "";
232
204
  }
233
205
 
234
- function previewTool(args: Record<string, unknown>): string {
235
- return compact(JSON.stringify(args));
236
- }
237
-
238
- function previewMessage(message: ContextMessage): string {
239
- if (message.role === "user" || message.role === "custom") {
240
- if (typeof message.content === "string") return compact(message.content);
241
- const text = message.content.find((part) => part.type === "text");
242
- return text?.type === "text" ? compact(text.text) : "";
243
- }
244
- if (message.role === "bashExecution") return compact(`${message.command}: ${message.output}`);
245
- if (message.role === "assistant") return previewAssistant(message.content);
246
- if (message.role === "toolResult") {
247
- const text = message.content.find((part) => part.type === "text");
248
- return text?.type === "text" ? compact(text.text) : "";
249
- }
250
- return compact(message.summary);
251
- }
252
-
253
- function outlineRowIds(message: ContextMessage): string[] {
254
- if (message.role !== "custom" || message.customType !== "tau.explore.outline") return [];
255
- if (!isRecord(message.details) || typeof message.details.rowId !== "string") return [];
256
- return [message.details.rowId];
206
+ function previewUser(message: Extract<ContextMessage, { role: "user" }>): string {
207
+ if (typeof message.content === "string") return compact(message.content);
208
+ const text = message.content.find((part) => part.type === "text");
209
+ return text?.type === "text" ? compact(text.text) : "";
257
210
  }
258
211
 
259
212
  function compact(value: string): string {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shanepadgett/tau-agent",
3
- "version": "0.30.0",
3
+ "version": "0.31.0",
4
4
  "description": "Tau is a custom agentic harness built with pi extensions",
5
5
  "type": "module",
6
6
  "main": "./src/index.ts",
@@ -35,7 +35,7 @@
35
35
  ],
36
36
  "dependencies": {
37
37
  "@ast-grep/wasm": "0.45.0",
38
- "@shanepadgett/tau-tui": "0.30.0",
38
+ "@shanepadgett/tau-tui": "0.31.0",
39
39
  "@vscode/tree-sitter-wasm": "0.3.1",
40
40
  "image-size": "2.0.2",
41
41
  "smol-toml": "1.7.0",