@shanepadgett/tau-agent 0.16.0 → 0.18.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.
Files changed (55) hide show
  1. package/docs/subagents.md +25 -6
  2. package/extensions/attention/README.md +1 -0
  3. package/extensions/attention/index.ts +38 -0
  4. package/extensions/context/README.md +13 -3
  5. package/extensions/context/definitions.ts +3 -15
  6. package/extensions/context/evidence.ts +516 -0
  7. package/extensions/context/index.ts +114 -116
  8. package/extensions/context/panel.ts +60 -0
  9. package/extensions/context/settings.ts +30 -1
  10. package/extensions/context/sync.ts +171 -686
  11. package/extensions/context/validation.ts +4 -1
  12. package/extensions/context/write-scope.ts +109 -0
  13. package/extensions/context-pruning/README.md +22 -0
  14. package/extensions/context-pruning/file-evidence.ts +265 -0
  15. package/extensions/context-pruning/index.ts +379 -0
  16. package/extensions/context-pruning/projection.ts +108 -0
  17. package/extensions/context-pruning/prune.ts +346 -0
  18. package/extensions/context-pruning/render.ts +179 -0
  19. package/extensions/context-pruning/settings.ts +41 -0
  20. package/extensions/explore/README.md +3 -1
  21. package/extensions/explore/autoread.ts +85 -22
  22. package/extensions/explore/full-file-knowledge.ts +234 -0
  23. package/extensions/explore/index.ts +2 -3
  24. package/extensions/explore/read-cache.ts +150 -86
  25. package/extensions/explore/read-snapshots.ts +17 -4
  26. package/extensions/explore/read.ts +57 -38
  27. package/extensions/footer/index.ts +62 -60
  28. package/extensions/run-summary/index.ts +5 -5
  29. package/extensions/silent-command-runner/README.md +1 -1
  30. package/extensions/silent-command-runner/index.ts +45 -28
  31. package/extensions/soul/prompt.ts +3 -1
  32. package/extensions/subagent/README.md +21 -5
  33. package/extensions/subagent/agents/context-sync.md +97 -0
  34. package/extensions/subagent/agents/{generalist.md → dormant/generalist.md} +6 -0
  35. package/extensions/subagent/agents/{scout.md → dormant/scout.md} +6 -0
  36. package/extensions/subagent/agents/review.md +48 -0
  37. package/extensions/subagent/agents/web-research.md +6 -0
  38. package/extensions/subagent/agents.ts +15 -2
  39. package/extensions/subagent/cmux-dashboard.ts +454 -0
  40. package/extensions/subagent/index.ts +181 -234
  41. package/extensions/subagent/render.ts +1 -1
  42. package/extensions/subagent/resume.ts +78 -0
  43. package/extensions/subagent/run.ts +213 -118
  44. package/extensions/subagent/runtime.ts +856 -0
  45. package/extensions/subagent/session-resource.ts +169 -0
  46. package/extensions/tau-help/help.md +6 -2
  47. package/extensions/turn-budget/index.ts +8 -36
  48. package/package.json +2 -2
  49. package/schemas/tau.schema.json +48 -1
  50. package/shared/context-pruning-state.ts +364 -0
  51. package/shared/events.ts +18 -0
  52. package/shared/model-fallback/index.ts +21 -10
  53. package/shared/model-fallback/types.ts +5 -3
  54. package/shared/settings/load.ts +78 -1
  55. package/shared/tool-row-state.ts +21 -1
package/docs/subagents.md CHANGED
@@ -5,11 +5,19 @@ Tau's `subagent` tool delegates one focused task to an isolated child Pi session
5
5
  Each fresh call returns a thread ID. Continue that thread when feedback or follow-up work depends on the child's prior reads and reasoning:
6
6
 
7
7
  ```text
8
- subagent({ agent: "scout", task: "Trace configuration loading" })
8
+ subagent({ agent: "review", task: "Review configuration loading" })
9
9
  subagent({ thread: "thread-1", task: "Now check whether this proposed fix covers every caller" })
10
10
  ```
11
11
 
12
- Retained threads keep their child conversation and tool results for the current parent session. Start fresh for unrelated work or when earlier context is stale or oversized. Tau keeps up to 16 threads and evicts the least recently used idle thread when needed.
12
+ Retained threads keep their complete child conversation for five minutes after the latest child response. A later follow-up keeps the same thread, agent, model, thinking level, tools, and cwd, but starts a clean child session. Tau supplies prior tasks, exact terminal results, and paths passed through `files`; it does not run a summarization request. Old source, tool history, intermediate responses, and thinking are absent, so the child reads current source before relying on a retained path. Start fresh for unrelated work. Tau keeps up to 16 threads and evicts the least recently used idle thread when needed.
13
+
14
+ If the relevant files are already known, autoread them into a fresh or retained child turn:
15
+
16
+ ```text
17
+ subagent({ agent: "review", task: "Review the runtime change", files: ["src/runtime.ts", "test/runtime.test.ts"] })
18
+ ```
19
+
20
+ Paths may be relative to the parent's current working directory or absolute. Tau reads current, line-numbered snapshots when the turn starts. Unreadable files produce failed context entries without stopping the child. Keep the list focused because complete snapshots consume the child's context window.
13
21
 
14
22
  ## Where definitions live
15
23
 
@@ -18,7 +26,7 @@ Retained threads keep their child conversation and tool results for the current
18
26
  | **User (global)** | `~/.pi/agent/tau/agents/*.md` | You want the agent in every project |
19
27
  | **Project** | nearest trusted `.pi/tau/agents/*.md` | Repo-specific helpers |
20
28
 
21
- Precedence: **project overrides user**, which overrides Tau's built-ins (`scout`, `web-research`). Duplicate names in one scope are invalid.
29
+ Precedence: **project overrides user**, which overrides Tau's built-ins (`review`, `web-research`). Duplicate names in one scope are invalid.
22
30
 
23
31
  ## Definition format
24
32
 
@@ -31,6 +39,10 @@ description: Inspect API declarations and usage
31
39
  tools:
32
40
  - read
33
41
  - grep
42
+ names:
43
+ - Ledger
44
+ - Quill
45
+ - Beacon
34
46
  model: openai-codex/gpt-5.4-mini
35
47
  thinking: medium
36
48
  ---
@@ -46,25 +58,32 @@ Required:
46
58
 
47
59
  Optional:
48
60
 
61
+ - `names`: a non-empty list of unique display names
49
62
  - `model` as `provider/model`
50
63
  - `thinking`: `off`, `minimal`, `low`, `medium`, `high`, `xhigh`, or `max`
51
64
 
52
65
  Named tools and configured models must exist in the normally loaded child Pi environment (including tools from installed packages such as Tau).
53
66
 
67
+ Tau assigns one display name to each fresh child and keeps it for follow-up turns on that thread. It cycles through the list in order. When the list wraps, reused names get numeric suffixes such as `Ledger-2`, so any number of concurrent children can still have distinct names. Without `names`, the agent name is used as a one-item pool.
68
+
54
69
  ## Runtime rules
55
70
 
56
71
  - Children use the parent's cwd and inherit model/thinking unless the definition overrides them.
57
72
  - Children do not receive the parent conversation.
58
- - Follow-up calls reuse the retained child's conversation, model, thinking level, tools, and cwd.
73
+ - Calls can include `files` to autoread line-numbered snapshots into that child turn.
74
+ - Follow-up calls within five minutes reuse the complete child conversation. Colder calls resume from exact prior results and relevant paths in a clean session.
75
+ - Cold resume keeps the selected model, thinking level, tools, cwd, definition, display name, and thread ID.
59
76
  - Children load only the extensions that own their declared tools. Unrelated extension hooks do not run in child sessions.
60
77
  - At most four children run at once; extra calls wait in order.
61
78
  - Calls to the same retained thread run one at a time.
79
+ - Display names identify children in Tau's tool rows and cmux dashboard; thread IDs remain the identifiers used for continuation calls.
62
80
  - Returned text is capped (50 KB / 2,000 lines); full truncated output is saved to a private temp file.
81
+ - Interactive cmux sessions get one temporary Markdown dashboard for waiting and running invocations. It is observational only: cmux latency or failure cannot delay, fail, or reorder children. The dashboard closes shortly after the active cohort finishes. Print mode never opens it.
63
82
 
64
83
  ## Built-ins
65
84
 
66
- - `generalist` — focused analysis, review, implementation, or mixed work when no narrower agent fits
67
- - `scout` — local exploration with `read`, `grep`, `find`, `ls`
85
+ - `review` — adversarial, read-only review for correctness, runtime risks, duplication, and over- or under-engineering
68
86
  - `web-research` — `websearch`, `codesearch`, `webfetch`
87
+ - `context-sync` — maps meaningful uncommitted work into `.pi/contexts`. Offered to the coding agent when `extensions.context.sync.enabled` and `sync.automation` are true. Manual `/context-sync` remains when sync is enabled with `automation` false. Validation can auto-run it when `validation.enabled` and `sync.enabled`
69
88
 
70
89
  Ask Tau to delegate, or let it call `subagent` with an agent name and task.
@@ -5,6 +5,7 @@ Sends a terminal-driven attention notification when Tau is ready for input, fini
5
5
  ## Behavior
6
6
 
7
7
  - Emits an attention notification after the agent settles with no automatic continuation pending.
8
+ - Waits for automatic post-turn checks before deciding whether the agent is ready for input.
8
9
  - Emits an attention notification on `session_compact`.
9
10
  - Emits an attention notification on `session_tree` when it includes a branch summary.
10
11
  - Listens for shared event `tau:agent.blocked` when Tau is waiting on user input.
@@ -25,6 +25,10 @@ function playMacOSSound(pi: ExtensionAPI): void {
25
25
  }
26
26
 
27
27
  export default function attentionExtension(pi: ExtensionAPI): void {
28
+ const holds = new Set<string>();
29
+ let pendingAttention: (() => void) | undefined;
30
+ let discardNextSettlement = false;
31
+
28
32
  function notify(data: { title?: string; body?: string }): void {
29
33
  const raw: unknown = data;
30
34
  const record = raw && typeof raw === "object" ? (raw as Record<string, unknown>) : {};
@@ -65,9 +69,37 @@ export default function attentionExtension(pi: ExtensionAPI): void {
65
69
  }
66
70
 
67
71
  onTauEvent(pi, "attention.agent-blocked", "tau:agent.blocked", notify);
72
+ onTauEvent(pi, "attention.hold-acquire", "tau:attention.hold.acquire", ({ id }) => {
73
+ holds.add(id);
74
+ });
75
+ onTauEvent(pi, "attention.hold-release", "tau:attention.hold.release", ({ id, disposition }) => {
76
+ if (!holds.delete(id)) return;
77
+ if (disposition === "discard") {
78
+ if (pendingAttention) pendingAttention = undefined;
79
+ else discardNextSettlement = true;
80
+ }
81
+ if (holds.size > 0 || !pendingAttention) return;
82
+ const notifyPending = pendingAttention;
83
+ pendingAttention = undefined;
84
+ notifyPending();
85
+ });
86
+
87
+ pi.on("session_start", () => {
88
+ holds.clear();
89
+ pendingAttention = undefined;
90
+ discardNextSettlement = false;
91
+ });
68
92
 
69
93
  pi.on("agent_settled", (_event, ctx) => {
70
94
  if (ctx.mode === "print") return;
95
+ if (discardNextSettlement) {
96
+ discardNextSettlement = false;
97
+ return;
98
+ }
99
+ if (holds.size > 0) {
100
+ pendingAttention = () => notify({ title: DEFAULT_TITLE, body: DEFAULT_BODY });
101
+ return;
102
+ }
71
103
  notify({ title: DEFAULT_TITLE, body: DEFAULT_BODY });
72
104
  });
73
105
 
@@ -80,4 +112,10 @@ export default function attentionExtension(pi: ExtensionAPI): void {
80
112
  if (ctx.mode === "print" || !event.summaryEntry) return;
81
113
  notify({ title: DEFAULT_TITLE, body: BRANCH_SUMMARY_BODY });
82
114
  });
115
+
116
+ pi.on("session_shutdown", () => {
117
+ holds.clear();
118
+ pendingAttention = undefined;
119
+ discardNextSettlement = false;
120
+ });
83
121
  }
@@ -2,16 +2,26 @@
2
2
 
3
3
  Context stores reusable repository work scopes in `.pi/contexts`. Folder names become selector tabs, TOML files become concepts, and TOML sections become selectable entries.
4
4
 
5
- Use `/context` to select entries. Entry `files` are injected through Tau autoread. Entry `anchors` supply lazy navigation paths that the agent can grep or read in ranges when needed. Use `/context-sync` to reconcile affected scopes from the current Git changes. Tau validates both file classes as context membership after agent turns and asks the agent to sync uncovered changed files or stale references automatically.
5
+ Use `/context` to select entries. Entry `files` are injected through Tau autoread. Entry `anchors` supply lazy navigation paths that the agent can grep or read in ranges when needed.
6
6
 
7
- Validation is disabled by default. Enable it globally or per project in Tau settings:
7
+ After meaningful uncommitted work (new/moved ownership, not trivial already-covered polish), the coding agent should run the `context-sync` subagent so `.pi/contexts` stays aligned. Context sync catalogs durable code and long-lived documentation. Scratch pads, working plans, interviews, rough ideas, and other temporary artifacts should stay out; add recurring transient paths to `validation.ignoreGlobs`. Humans can also run `/context-sync` or `/context-sync <nudge>` and press Escape to cancel a running sync. It walks domain → concept → entry → membership, edits only `.pi/contexts` with `patch`, and the harness verifies write scope plus catalog invariants afterward. Out-of-scope writes are restored and the run fails. Optional nudge text soft-steers judgment without skipping evidence.
8
+
9
+ Sync surface is configurable:
10
+
11
+ - `sync.enabled` (default true) — master switch. Off: no `/context-sync`, parent cannot call `context-sync`, validation does not auto-run sync.
12
+ - `sync.automation` (default true) — when false with sync still enabled: manual `/context-sync` only (coding agent does not see context-sync). Validation auto-run still works if validation is enabled.
13
+ - `validation.enabled` (default false) — after agent turns, check membership and auto-run context-sync on failure (requires `sync.enabled`).
8
14
 
9
15
  ```json
10
16
  {
11
17
  "extensions": {
12
18
  "context": {
19
+ "sync": {
20
+ "enabled": true,
21
+ "automation": true
22
+ },
13
23
  "validation": {
14
- "enabled": true,
24
+ "enabled": true,
15
25
  "ignoreGlobs": ["generated/**"]
16
26
  }
17
27
  }
@@ -1,4 +1,4 @@
1
- import { access, readFile, readdir, stat } from "node:fs/promises";
1
+ import { access, readFile, readdir } from "node:fs/promises";
2
2
  import { homedir } from "node:os";
3
3
  import { basename, dirname, extname, join, relative, resolve, sep } from "node:path";
4
4
  import { parse } from "smol-toml";
@@ -81,13 +81,13 @@ export async function findProjectRoot(cwd: string): Promise<string> {
81
81
  return gitRoot ?? resolve(cwd);
82
82
  }
83
83
 
84
- export function validSlug(value: string, label: string): string {
84
+ function validSlug(value: string, label: string): string {
85
85
  const slug = value.trim();
86
86
  if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(slug)) throw new Error(`${label} must use lowercase kebab-case: ${value}`);
87
87
  return slug;
88
88
  }
89
89
 
90
- export function normalizeProjectPath(root: string, input: string): string {
90
+ function normalizeProjectPath(root: string, input: string): string {
91
91
  const absolute = resolve(root, input.trim().replace(/^@/, ""));
92
92
  const path = relative(root, absolute).split(sep).join("/");
93
93
  if (!path || path === "." || path === ".." || path.startsWith("../"))
@@ -103,18 +103,6 @@ export function contextEntryPaths(entry: Pick<ContextEntry, "files" | "anchors">
103
103
  return sortedUnique([...entry.files, ...entry.anchors]);
104
104
  }
105
105
 
106
- export async function requireFiles(root: string, inputs: readonly string[]): Promise<string[]> {
107
- const files = sortedUnique(inputs.map((input) => normalizeProjectPath(root, input)));
108
- for (const file of files) {
109
- try {
110
- if (!(await stat(join(root, file))).isFile()) throw new Error();
111
- } catch {
112
- throw new Error(`Context file does not exist: ${file}`);
113
- }
114
- }
115
- return files;
116
- }
117
-
118
106
  export async function loadContextEntries(root: string): Promise<ContextEntry[]> {
119
107
  const contextsRoot = join(root, ".pi", "contexts");
120
108
  if (!(await pathExists(contextsRoot))) return [];