@shanepadgett/tau-agent 0.16.0 → 0.17.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/docs/subagents.md +25 -6
- package/extensions/attention/README.md +1 -0
- package/extensions/attention/index.ts +38 -0
- package/extensions/context/README.md +13 -3
- package/extensions/context/definitions.ts +3 -15
- package/extensions/context/evidence.ts +516 -0
- package/extensions/context/index.ts +114 -116
- package/extensions/context/panel.ts +60 -0
- package/extensions/context/settings.ts +30 -1
- package/extensions/context/sync.ts +171 -686
- package/extensions/context/validation.ts +4 -1
- package/extensions/context/write-scope.ts +109 -0
- package/extensions/context-pruning/README.md +22 -0
- package/extensions/context-pruning/file-evidence.ts +265 -0
- package/extensions/context-pruning/index.ts +379 -0
- package/extensions/context-pruning/projection.ts +108 -0
- package/extensions/context-pruning/prune.ts +346 -0
- package/extensions/context-pruning/render.ts +179 -0
- package/extensions/context-pruning/settings.ts +41 -0
- package/extensions/explore/README.md +3 -1
- package/extensions/explore/autoread.ts +85 -22
- package/extensions/explore/full-file-knowledge.ts +234 -0
- package/extensions/explore/index.ts +2 -3
- package/extensions/explore/read-cache.ts +150 -86
- package/extensions/explore/read-snapshots.ts +17 -4
- package/extensions/explore/read.ts +57 -38
- package/extensions/footer/index.ts +62 -60
- package/extensions/run-summary/index.ts +5 -5
- package/extensions/silent-command-runner/README.md +1 -1
- package/extensions/silent-command-runner/index.ts +45 -28
- package/extensions/soul/prompt.ts +3 -1
- package/extensions/subagent/README.md +21 -5
- package/extensions/subagent/agents/context-sync.md +90 -0
- package/extensions/subagent/agents/{generalist.md → dormant/generalist.md} +6 -0
- package/extensions/subagent/agents/{scout.md → dormant/scout.md} +6 -0
- package/extensions/subagent/agents/review.md +48 -0
- package/extensions/subagent/agents/web-research.md +6 -0
- package/extensions/subagent/agents.ts +15 -2
- package/extensions/subagent/cmux-dashboard.ts +454 -0
- package/extensions/subagent/index.ts +181 -234
- package/extensions/subagent/render.ts +1 -1
- package/extensions/subagent/resume.ts +78 -0
- package/extensions/subagent/run.ts +213 -118
- package/extensions/subagent/runtime.ts +856 -0
- package/extensions/subagent/session-resource.ts +169 -0
- package/extensions/tau-help/help.md +6 -2
- package/extensions/turn-budget/index.ts +8 -36
- package/package.json +2 -2
- package/schemas/tau.schema.json +48 -1
- package/shared/context-pruning-state.ts +364 -0
- package/shared/events.ts +18 -0
- package/shared/model-fallback/index.ts +21 -10
- package/shared/model-fallback/types.ts +5 -3
- package/shared/settings/load.ts +78 -1
- package/shared/tool-row-state.ts +21 -1
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createAgentSession,
|
|
3
|
+
DefaultResourceLoader,
|
|
4
|
+
getAgentDir,
|
|
5
|
+
ModelRuntime,
|
|
6
|
+
SessionManager,
|
|
7
|
+
type AgentSession,
|
|
8
|
+
type ExtensionContext,
|
|
9
|
+
} from "@earendil-works/pi-coding-agent";
|
|
10
|
+
import type { AgentDefinition, ThinkingLevel } from "./agents.ts";
|
|
11
|
+
|
|
12
|
+
const CHILD_UI_BLOCKED_METHODS = new Set([
|
|
13
|
+
"setEditorComponent",
|
|
14
|
+
"setFooter",
|
|
15
|
+
"setStatus",
|
|
16
|
+
"setTitle",
|
|
17
|
+
"setWidget",
|
|
18
|
+
"setWorkingIndicator",
|
|
19
|
+
]);
|
|
20
|
+
|
|
21
|
+
type SelectedModel = NonNullable<ExtensionContext["model"]>;
|
|
22
|
+
type SelectedProvider = NonNullable<ReturnType<ExtensionContext["modelRegistry"]["getProvider"]>>;
|
|
23
|
+
|
|
24
|
+
export interface SubagentSessionInputs {
|
|
25
|
+
definition: AgentDefinition;
|
|
26
|
+
extensionPaths: readonly string[];
|
|
27
|
+
cwd: string;
|
|
28
|
+
model: SelectedModel;
|
|
29
|
+
modelName: string;
|
|
30
|
+
provider: SelectedProvider;
|
|
31
|
+
runtimeApiKey: string | undefined;
|
|
32
|
+
thinkingLevel: ThinkingLevel;
|
|
33
|
+
bindTarget: { mode: "print" } | { mode: "tui"; uiContext: ExtensionContext["ui"] };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface SubagentSessionResource {
|
|
37
|
+
readonly inputs: SubagentSessionInputs;
|
|
38
|
+
readonly session: AgentSession;
|
|
39
|
+
dispose(): Promise<void>;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function childUiContext(ui: ExtensionContext["ui"]): ExtensionContext["ui"] {
|
|
43
|
+
return new Proxy(ui, {
|
|
44
|
+
get(target, property, receiver) {
|
|
45
|
+
if (typeof property === "string" && CHILD_UI_BLOCKED_METHODS.has(property)) return () => undefined;
|
|
46
|
+
const value = Reflect.get(target, property, receiver) as unknown;
|
|
47
|
+
return typeof value === "function" ? value.bind(target) : value;
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export async function resolveSubagentSessionInputs(options: {
|
|
53
|
+
definition: AgentDefinition;
|
|
54
|
+
extensionPaths: readonly string[];
|
|
55
|
+
ctx: ExtensionContext;
|
|
56
|
+
parentThinkingLevel: string;
|
|
57
|
+
signal: AbortSignal;
|
|
58
|
+
onWarning?: (warning: string) => void;
|
|
59
|
+
}): Promise<SubagentSessionInputs> {
|
|
60
|
+
const { definition, extensionPaths, ctx, signal, onWarning } = options;
|
|
61
|
+
let model = ctx.model;
|
|
62
|
+
let thinkingLevel = options.parentThinkingLevel as ThinkingLevel;
|
|
63
|
+
let selectedAuth: Awaited<ReturnType<ExtensionContext["modelRegistry"]["getApiKeyAndHeaders"]>> | undefined;
|
|
64
|
+
if (definition.model) {
|
|
65
|
+
const separator = definition.model.indexOf("/");
|
|
66
|
+
const configured = ctx.modelRegistry.find(
|
|
67
|
+
definition.model.slice(0, separator),
|
|
68
|
+
definition.model.slice(separator + 1),
|
|
69
|
+
);
|
|
70
|
+
if (!configured) onWarning?.(`model ${definition.model} is unavailable; using parent model`);
|
|
71
|
+
else {
|
|
72
|
+
const auth = await ctx.modelRegistry.getApiKeyAndHeaders(configured);
|
|
73
|
+
if (!auth.ok) onWarning?.(`model ${definition.model} is unavailable: ${auth.error}; using parent model`);
|
|
74
|
+
else {
|
|
75
|
+
model = configured;
|
|
76
|
+
selectedAuth = auth;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
if (definition.thinking) {
|
|
81
|
+
const mapped = model?.thinkingLevelMap?.[definition.thinking];
|
|
82
|
+
const unsupported =
|
|
83
|
+
!model?.reasoning ||
|
|
84
|
+
mapped === null ||
|
|
85
|
+
((definition.thinking === "xhigh" || definition.thinking === "max") && mapped === undefined);
|
|
86
|
+
if (unsupported)
|
|
87
|
+
onWarning?.(`thinking ${definition.thinking} is unavailable for the selected model; using parent thinking`);
|
|
88
|
+
else thinkingLevel = definition.thinking;
|
|
89
|
+
}
|
|
90
|
+
if (!model) throw new Error(`Agent ${definition.name} startup failed: parent has no model`);
|
|
91
|
+
const auth = selectedAuth ?? (await ctx.modelRegistry.getApiKeyAndHeaders(model));
|
|
92
|
+
if (!auth.ok) throw new Error(`Agent ${definition.name} startup failed: ${auth.error}`);
|
|
93
|
+
const provider = ctx.modelRegistry.getProvider(model.provider);
|
|
94
|
+
if (!provider) throw new Error(`Agent ${definition.name} startup failed: provider ${model.provider} is unavailable`);
|
|
95
|
+
if (signal.aborted) throw new Error(`Agent ${definition.name} startup aborted`);
|
|
96
|
+
return {
|
|
97
|
+
definition,
|
|
98
|
+
extensionPaths: [...extensionPaths],
|
|
99
|
+
cwd: ctx.cwd,
|
|
100
|
+
model,
|
|
101
|
+
modelName: `${model.provider}/${model.id}`,
|
|
102
|
+
provider,
|
|
103
|
+
runtimeApiKey:
|
|
104
|
+
auth.apiKey && provider.auth.apiKey && !ctx.modelRegistry.isUsingOAuth(model) ? auth.apiKey : undefined,
|
|
105
|
+
thinkingLevel,
|
|
106
|
+
bindTarget:
|
|
107
|
+
ctx.mode === "tui" && ctx.hasUI ? { mode: "tui", uiContext: childUiContext(ctx.ui) } : { mode: "print" },
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export async function createSubagentSessionResource(
|
|
112
|
+
inputs: SubagentSessionInputs,
|
|
113
|
+
signal: AbortSignal,
|
|
114
|
+
): Promise<SubagentSessionResource> {
|
|
115
|
+
let session: AgentSession | undefined;
|
|
116
|
+
try {
|
|
117
|
+
if (signal.aborted) throw new Error(`Agent ${inputs.definition.name} startup aborted`);
|
|
118
|
+
const modelRuntime = await ModelRuntime.create();
|
|
119
|
+
modelRuntime.registerNativeProvider(inputs.provider);
|
|
120
|
+
if (inputs.runtimeApiKey !== undefined)
|
|
121
|
+
await modelRuntime.setRuntimeApiKey(inputs.model.provider, inputs.runtimeApiKey);
|
|
122
|
+
if (signal.aborted) throw new Error(`Agent ${inputs.definition.name} startup aborted`);
|
|
123
|
+
const resourceLoader = new DefaultResourceLoader({
|
|
124
|
+
cwd: inputs.cwd,
|
|
125
|
+
agentDir: getAgentDir(),
|
|
126
|
+
noExtensions: true,
|
|
127
|
+
additionalExtensionPaths: [...inputs.extensionPaths],
|
|
128
|
+
});
|
|
129
|
+
await resourceLoader.reload();
|
|
130
|
+
if (signal.aborted) throw new Error(`Agent ${inputs.definition.name} startup aborted`);
|
|
131
|
+
const created = await createAgentSession({
|
|
132
|
+
cwd: inputs.cwd,
|
|
133
|
+
model: inputs.model,
|
|
134
|
+
modelRuntime,
|
|
135
|
+
thinkingLevel: inputs.thinkingLevel,
|
|
136
|
+
tools: inputs.definition.tools,
|
|
137
|
+
excludeTools: ["subagent"],
|
|
138
|
+
resourceLoader,
|
|
139
|
+
sessionManager: SessionManager.inMemory(inputs.cwd),
|
|
140
|
+
});
|
|
141
|
+
session = created.session;
|
|
142
|
+
if (signal.aborted) throw new Error(`Agent ${inputs.definition.name} startup aborted`);
|
|
143
|
+
await session.bindExtensions(inputs.bindTarget);
|
|
144
|
+
if (signal.aborted) throw new Error(`Agent ${inputs.definition.name} startup aborted`);
|
|
145
|
+
const active = session.getActiveToolNames().sort();
|
|
146
|
+
const expected = [...inputs.definition.tools].sort();
|
|
147
|
+
if (active.join("\0") !== expected.join("\0") || active.includes("subagent")) {
|
|
148
|
+
const missing = expected.filter((tool) => !active.includes(tool));
|
|
149
|
+
throw new Error(
|
|
150
|
+
`Agent ${inputs.definition.name} startup failed: unavailable tools: ${missing.join(", ") || "active tool mismatch"}`,
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
let disposed = false;
|
|
154
|
+
return {
|
|
155
|
+
inputs,
|
|
156
|
+
session,
|
|
157
|
+
async dispose() {
|
|
158
|
+
if (disposed) return;
|
|
159
|
+
disposed = true;
|
|
160
|
+
if (session?.isStreaming) await session.abort().catch(() => undefined);
|
|
161
|
+
session?.dispose();
|
|
162
|
+
},
|
|
163
|
+
};
|
|
164
|
+
} catch (error) {
|
|
165
|
+
if (session?.isStreaming) await session.abort().catch(() => undefined);
|
|
166
|
+
session?.dispose();
|
|
167
|
+
throw error;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
@@ -28,7 +28,11 @@ Adds `/commit` for semantic commit grouping, review, and committing selected rep
|
|
|
28
28
|
|
|
29
29
|
## context
|
|
30
30
|
|
|
31
|
-
Adds `/context` to select reusable repository work scopes from `.pi/contexts`, and `/context-sync`
|
|
31
|
+
Adds `/context` to select reusable repository work scopes from `.pi/contexts`, and `/context-sync` or `/context-sync <nudge>` for human-driven catalog sync (optional nudge). Escape cancels a running manual sync. When `sync.automation` is on, the coding agent can also run the `context-sync` subagent after meaningful uncommitted work. `sync.enabled` is the master switch for command, automation, and validation auto-run. Entry `files` are autoread; entry `anchors` are unloaded navigation paths. Context validation is off by default; when on (and sync enabled), Tau auto-runs context-sync on failure. Folder names are tabs, TOML files are concepts, and TOML sections are selectable entries.
|
|
32
|
+
|
|
33
|
+
## context-pruning
|
|
34
|
+
|
|
35
|
+
Gives the agent `context_prune` for removing substantial stale tool evidence after broad exploration converges. The agent records durable conclusions before creating an anchor, can retain exact exchanges or current file knowledge, and continues the task automatically. Context markers occasionally steer the agent after tool-heavy growth and become more direct under pressure. Run `/prune` with no arguments to request an anchor manually. Session history remains unchanged, and anchors, markers, and pruned-row state follow the active branch.
|
|
32
36
|
|
|
33
37
|
## explore
|
|
34
38
|
|
|
@@ -88,7 +92,7 @@ Adds `Alt+S` to stash the current prompt draft and `/pop` to browse stashed draf
|
|
|
88
92
|
|
|
89
93
|
## subagent
|
|
90
94
|
|
|
91
|
-
Gives Tau a subagent delegation tool for isolated, focused work. Tau can continue a retained child thread when follow-up work depends on its prior reads and reasoning. You can also create your own subagents in the supported subagent directories. Ask Tau how to do it and have it consult the extension’s own documentation; the built-in agents show the pattern. Each subagent can register its own model and
|
|
95
|
+
Gives Tau a subagent delegation tool for isolated, focused work. The built-in `review` agent performs adversarial, read-only code reviews; `web-research` handles external research. Known files can be autoread as line-numbered snapshots into a fresh or retained child turn. Tau can continue a retained child thread when follow-up work depends on its prior reads and reasoning. You can also create your own subagents in the supported subagent directories. Ask Tau how to do it and have it consult the extension’s own documentation; the built-in agents show the pattern. Each subagent can register its own model, tools, and pool of display names. Reused pool names get numeric suffixes. In interactive cmux sessions, Tau opens one temporary Markdown dashboard for live subagent progress; it does not change how children run and closes shortly after the active cohort finishes.
|
|
92
96
|
|
|
93
97
|
## tau-help
|
|
94
98
|
|
|
@@ -20,15 +20,12 @@ interface MarkerDetails {
|
|
|
20
20
|
used: number;
|
|
21
21
|
cap: number;
|
|
22
22
|
extended: boolean;
|
|
23
|
-
sequence: number;
|
|
24
23
|
}
|
|
25
24
|
|
|
26
25
|
export default function turnBudgetExtension(pi: ExtensionAPI): void {
|
|
27
26
|
let settings = normalizeSettings(turnBudgetSettings.defaults);
|
|
28
27
|
let turnCount = 0;
|
|
29
28
|
let softCap = settings.turnLimit;
|
|
30
|
-
let nextMarkerSequence = 1;
|
|
31
|
-
let activeMarkerSequence: number | undefined;
|
|
32
29
|
|
|
33
30
|
pi.registerMessageRenderer<MarkerDetails>(MARKER_TYPE, (message, _options, theme) => {
|
|
34
31
|
const details = readMarkerDetails(message.details);
|
|
@@ -48,7 +45,6 @@ export default function turnBudgetExtension(pi: ExtensionAPI): void {
|
|
|
48
45
|
pi.on("agent_start", () => {
|
|
49
46
|
turnCount = 0;
|
|
50
47
|
softCap = settings.turnLimit;
|
|
51
|
-
activeMarkerSequence = undefined;
|
|
52
48
|
});
|
|
53
49
|
|
|
54
50
|
pi.on("turn_end", (event) => {
|
|
@@ -62,46 +58,24 @@ export default function turnBudgetExtension(pi: ExtensionAPI): void {
|
|
|
62
58
|
previousCap: softCap,
|
|
63
59
|
newCap: turnCount + settings.softCapIncrement,
|
|
64
60
|
};
|
|
65
|
-
|
|
66
|
-
sendTurnBudgetMessage(pi, hint, nextMarkerSequence);
|
|
67
|
-
nextMarkerSequence += 1;
|
|
61
|
+
sendTurnBudgetMessage(pi, hint);
|
|
68
62
|
softCap = hint.newCap;
|
|
69
63
|
return undefined;
|
|
70
64
|
}
|
|
71
65
|
if (turnCount % settings.nudgeEveryTurns === 0) {
|
|
72
66
|
const hint: Hint = { kind: "normal", used: turnCount, cap: softCap };
|
|
73
|
-
|
|
74
|
-
sendTurnBudgetMessage(pi, hint, nextMarkerSequence);
|
|
75
|
-
nextMarkerSequence += 1;
|
|
67
|
+
sendTurnBudgetMessage(pi, hint);
|
|
76
68
|
}
|
|
77
69
|
return undefined;
|
|
78
70
|
});
|
|
79
|
-
|
|
80
|
-
pi.on("context", (event) => {
|
|
81
|
-
const messages = event.messages.filter(
|
|
82
|
-
(message) =>
|
|
83
|
-
message.role !== "custom" || message.customType !== MARKER_TYPE || markerIsActive(message.details),
|
|
84
|
-
);
|
|
85
|
-
if (messages.length === event.messages.length) return undefined;
|
|
86
|
-
return { messages };
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
pi.on("after_provider_response", () => {
|
|
90
|
-
activeMarkerSequence = undefined;
|
|
91
|
-
});
|
|
92
|
-
function markerIsActive(value: unknown): boolean {
|
|
93
|
-
if (activeMarkerSequence === undefined) return false;
|
|
94
|
-
const details = readMarkerDetails(value);
|
|
95
|
-
return details?.sequence === activeMarkerSequence;
|
|
96
|
-
}
|
|
97
71
|
}
|
|
98
72
|
|
|
99
|
-
function sendTurnBudgetMessage(pi: ExtensionAPI, hint: Hint
|
|
73
|
+
function sendTurnBudgetMessage(pi: ExtensionAPI, hint: Hint): void {
|
|
100
74
|
pi.sendMessage<MarkerDetails>({
|
|
101
75
|
customType: MARKER_TYPE,
|
|
102
76
|
content: formatSteeringMessage(hint),
|
|
103
77
|
display: true,
|
|
104
|
-
details: markerDetails(hint
|
|
78
|
+
details: markerDetails(hint),
|
|
105
79
|
});
|
|
106
80
|
}
|
|
107
81
|
|
|
@@ -124,10 +98,10 @@ function normalizeSettings(value: typeof turnBudgetSettings.defaults): Settings
|
|
|
124
98
|
};
|
|
125
99
|
}
|
|
126
100
|
|
|
127
|
-
function markerDetails(hint: Hint
|
|
101
|
+
function markerDetails(hint: Hint): MarkerDetails {
|
|
128
102
|
return hint.kind === "extended"
|
|
129
|
-
? { used: hint.used, cap: hint.newCap, extended: true
|
|
130
|
-
: { used: hint.used, cap: hint.cap, extended: false
|
|
103
|
+
? { used: hint.used, cap: hint.newCap, extended: true }
|
|
104
|
+
: { used: hint.used, cap: hint.cap, extended: false };
|
|
131
105
|
}
|
|
132
106
|
|
|
133
107
|
function readMarkerDetails(value: unknown): MarkerDetails | undefined {
|
|
@@ -135,10 +109,8 @@ function readMarkerDetails(value: unknown): MarkerDetails | undefined {
|
|
|
135
109
|
const record = value as Record<string, unknown>;
|
|
136
110
|
const used = record.used;
|
|
137
111
|
const cap = record.cap;
|
|
138
|
-
const sequence = record.sequence;
|
|
139
112
|
if (typeof used !== "number" || !Number.isInteger(used) || used < 0) return undefined;
|
|
140
113
|
if (typeof cap !== "number" || !Number.isInteger(cap) || cap < 1) return undefined;
|
|
141
|
-
if (typeof sequence !== "number" || !Number.isInteger(sequence) || sequence < 1) return undefined;
|
|
142
114
|
if (typeof record.extended !== "boolean") return undefined;
|
|
143
|
-
return { used, cap, extended: record.extended
|
|
115
|
+
return { used, cap, extended: record.extended };
|
|
144
116
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shanepadgett/tau-agent",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.0",
|
|
4
4
|
"description": "Tau is a custom agentic harness built with pi extensions",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"README.md"
|
|
29
29
|
],
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@shanepadgett/tau-tui": "0.
|
|
31
|
+
"@shanepadgett/tau-tui": "0.17.0",
|
|
32
32
|
"@toon-format/toon": "2.3.0",
|
|
33
33
|
"smol-toml": "1.7.0"
|
|
34
34
|
},
|
package/schemas/tau.schema.json
CHANGED
|
@@ -15,13 +15,29 @@
|
|
|
15
15
|
"context": {
|
|
16
16
|
"type": "object",
|
|
17
17
|
"properties": {
|
|
18
|
+
"sync": {
|
|
19
|
+
"type": "object",
|
|
20
|
+
"properties": {
|
|
21
|
+
"enabled": {
|
|
22
|
+
"type": "boolean",
|
|
23
|
+
"default": true,
|
|
24
|
+
"description": "Master switch for context-sync. When false: no /context-sync, parent cannot call the agent, validation does not auto-run sync."
|
|
25
|
+
},
|
|
26
|
+
"automation": {
|
|
27
|
+
"type": "boolean",
|
|
28
|
+
"default": true,
|
|
29
|
+
"description": "When true with sync.enabled, the coding agent may call context-sync. When false, only manual /context-sync (validation auto-run still allowed if validation.enabled)."
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"additionalProperties": false
|
|
33
|
+
},
|
|
18
34
|
"validation": {
|
|
19
35
|
"type": "object",
|
|
20
36
|
"properties": {
|
|
21
37
|
"enabled": {
|
|
22
38
|
"type": "boolean",
|
|
23
39
|
"default": false,
|
|
24
|
-
"description": "Validate context membership after agent turns."
|
|
40
|
+
"description": "Validate context membership after agent turns and auto-run context-sync on failure (requires sync.enabled)."
|
|
25
41
|
},
|
|
26
42
|
"ignoreGlobs": {
|
|
27
43
|
"type": "array",
|
|
@@ -37,6 +53,37 @@
|
|
|
37
53
|
},
|
|
38
54
|
"additionalProperties": false
|
|
39
55
|
},
|
|
56
|
+
"contextPruning": {
|
|
57
|
+
"type": "object",
|
|
58
|
+
"properties": {
|
|
59
|
+
"enabled": {
|
|
60
|
+
"type": "boolean",
|
|
61
|
+
"default": true,
|
|
62
|
+
"description": "Enable context pruning."
|
|
63
|
+
},
|
|
64
|
+
"nudgeEveryPercent": {
|
|
65
|
+
"type": "integer",
|
|
66
|
+
"default": 20,
|
|
67
|
+
"minimum": 1,
|
|
68
|
+
"maximum": 100,
|
|
69
|
+
"description": "Context growth interval between automatic pruning hints."
|
|
70
|
+
},
|
|
71
|
+
"pressurePercent": {
|
|
72
|
+
"type": "integer",
|
|
73
|
+
"default": 50,
|
|
74
|
+
"minimum": 1,
|
|
75
|
+
"maximum": 99,
|
|
76
|
+
"description": "Context usage above which pruning hints become urgent."
|
|
77
|
+
},
|
|
78
|
+
"minimumReclaimTokens": {
|
|
79
|
+
"type": "integer",
|
|
80
|
+
"default": 8000,
|
|
81
|
+
"minimum": 1,
|
|
82
|
+
"description": "Minimum estimated tokens a prune must reclaim."
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
"additionalProperties": false
|
|
86
|
+
},
|
|
40
87
|
"footer": {
|
|
41
88
|
"type": "object",
|
|
42
89
|
"properties": {
|