harnesstrim 0.0.1
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/assets/adapter-hermes/plugin/__init__.py +242 -0
- package/assets/adapter-hermes/plugin/plugin.yaml +6 -0
- package/assets/adapter-pi/extension/harnesstrim.ts +62 -0
- package/assets/skills/compact-handoff/SKILL.md +33 -0
- package/assets/skills/debug-log-slim/SKILL.md +40 -0
- package/assets/skills/delegate-bulk/SKILL.md +41 -0
- package/assets/skills/delta-response/SKILL.md +26 -0
- package/assets/skills/delta-response/references/examples.md +33 -0
- package/assets/skills/review-delta/SKILL.md +30 -0
- package/assets/skills/scaffold-fast/SKILL.md +35 -0
- package/dist/cli.mjs +23628 -0
- package/package.json +36 -0
- package/src/cli.ts +238 -0
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
"""
|
|
2
|
+
HarnessTrim Hermes plugin — tool-output reduction via transform_tool_result hook.
|
|
3
|
+
|
|
4
|
+
When enabled, this plugin intercepts the ``transform_tool_result`` hook after every
|
|
5
|
+
tool call and slims noisy output (test runners, git diffs, build logs) to its signal
|
|
6
|
+
before the result enters the model's context. Dry-run mode logs what *would* be
|
|
7
|
+
reduced; active mode rewrites the result; telemetry (off by default) records every
|
|
8
|
+
reduction as a TrimEvent JSON line.
|
|
9
|
+
|
|
10
|
+
Run the ``harnesstrim reduce`` command via a subprocess so the reducers live in the
|
|
11
|
+
shared TypeScript/Node core rather than being reimplemented in Python.
|
|
12
|
+
|
|
13
|
+
Hook contract
|
|
14
|
+
-------------
|
|
15
|
+
``transform_tool_result`` is a built-in Hermes plugin hook:
|
|
16
|
+
- ``hermes_cli/plugins.py`` — listed in ``VALID_HOOKS``
|
|
17
|
+
- ``model_tools.py:1313-1345`` — core loop fires it after every tool call;
|
|
18
|
+
first callback to return a string replaces the result
|
|
19
|
+
- ``plugins/security-guidance/__init__.py`` — shipped plugin uses it
|
|
20
|
+
- ``tests/test_transform_tool_result_hook.py`` — dedicated test coverage
|
|
21
|
+
|
|
22
|
+
Callback receives: tool_name, args, result, task_id, session_id, tool_call_id,
|
|
23
|
+
turn_id, api_request_id, duration_ms, status, error_type, error_message.
|
|
24
|
+
Return a string to replace the result, or None to leave unchanged.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
import json
|
|
28
|
+
import os
|
|
29
|
+
import subprocess
|
|
30
|
+
import sys
|
|
31
|
+
from pathlib import Path
|
|
32
|
+
|
|
33
|
+
PLUGIN_DIR = Path(__file__).parent
|
|
34
|
+
|
|
35
|
+
CONFIG_DEFAULTS = {
|
|
36
|
+
"mode": "dryrun", # "dryrun" | "active" | "off"
|
|
37
|
+
"minLength": 400,
|
|
38
|
+
"telemetry": False, # explicit opt-in: tool output may contain sensitive data
|
|
39
|
+
"debug": False,
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
# Store telemetry in the active Hermes home. HERMES_HOME is profile-aware when set
|
|
43
|
+
# by Hermes; direct CLI usage falls back to the default profile.
|
|
44
|
+
METRICS_PATH = Path(os.environ.get("HERMES_HOME", str(Path.home() / ".hermes"))) / "harnesstrim-metrics.jsonl"
|
|
45
|
+
|
|
46
|
+
# Tool types whose output we consider for reduction.
|
|
47
|
+
REDUCER_TOOLS = frozenset({
|
|
48
|
+
"terminal",
|
|
49
|
+
"read_file",
|
|
50
|
+
"web_extract",
|
|
51
|
+
"search_files",
|
|
52
|
+
"browser_snapshot",
|
|
53
|
+
"vision_analyze",
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def register(ctx):
|
|
58
|
+
"""Register the HarnessTrim transform_tool_result hook."""
|
|
59
|
+
ctx.register_hook("transform_tool_result", on_tool_result)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _load_config():
|
|
63
|
+
"""Return the active plugin config from the environment (no global registry needed)."""
|
|
64
|
+
cfg = dict(CONFIG_DEFAULTS)
|
|
65
|
+
for key in CONFIG_DEFAULTS:
|
|
66
|
+
env_key = f"HARNESSTRIM_{key.upper()}"
|
|
67
|
+
val = os.environ.get(env_key)
|
|
68
|
+
if val is not None:
|
|
69
|
+
if isinstance(CONFIG_DEFAULTS[key], bool):
|
|
70
|
+
cfg[key] = val.lower() in ("1", "true", "yes")
|
|
71
|
+
elif isinstance(CONFIG_DEFAULTS[key], int):
|
|
72
|
+
try:
|
|
73
|
+
cfg[key] = int(val)
|
|
74
|
+
except ValueError:
|
|
75
|
+
pass
|
|
76
|
+
else:
|
|
77
|
+
cfg[key] = val
|
|
78
|
+
return cfg
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _find_harnesstrim_cli() -> str | None:
|
|
82
|
+
"""Locate the ``harnesstrim`` CLI binary on PATH."""
|
|
83
|
+
# Try PATH resolution
|
|
84
|
+
for p in os.environ.get("PATH", "").split(os.pathsep):
|
|
85
|
+
candidate = Path(p) / "harnesstrim"
|
|
86
|
+
if candidate.is_file() and os.access(candidate, os.X_OK):
|
|
87
|
+
return str(candidate.resolve())
|
|
88
|
+
# Check common monorepo dev location via the repo-root sentinel
|
|
89
|
+
return None
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _call_reducer(text: str, min_length: int) -> tuple[str, str | None]:
|
|
93
|
+
"""Shell out to ``harnesstrim reduce`` and return (slimmed_text, reducer_name).
|
|
94
|
+
|
|
95
|
+
Falls back to (original_text, None) if the CLI cannot be found or the pipe fails.
|
|
96
|
+
The reducer name is parsed from the ``--stats`` stderr line (e.g. ``test-output-slim``),
|
|
97
|
+
used only for telemetry when enabled.
|
|
98
|
+
"""
|
|
99
|
+
cli = _find_harnesstrim_cli()
|
|
100
|
+
if cli is None:
|
|
101
|
+
import warnings
|
|
102
|
+
warnings.warn(
|
|
103
|
+
"[harnesstrim] CLI not found on PATH — output not reduced. "
|
|
104
|
+
"Install CLI with: curl -fsSL https://harnesstrim.dev/install.sh | bash "
|
|
105
|
+
"or build from source: git clone https://github.com/harnesstrim/harnesstrim",
|
|
106
|
+
stacklevel=2,
|
|
107
|
+
)
|
|
108
|
+
return (text, None)
|
|
109
|
+
|
|
110
|
+
if len(text) < min_length:
|
|
111
|
+
return (text, None)
|
|
112
|
+
|
|
113
|
+
try:
|
|
114
|
+
result = subprocess.run(
|
|
115
|
+
[cli, "reduce", "--min-length", str(min_length), "--stats"],
|
|
116
|
+
input=text,
|
|
117
|
+
capture_output=True,
|
|
118
|
+
text=True,
|
|
119
|
+
timeout=30,
|
|
120
|
+
)
|
|
121
|
+
if result.returncode == 0 and result.stdout:
|
|
122
|
+
reducer = None
|
|
123
|
+
# stderr: "[harnesstrim reduce] <reducer>: <before> -> <after> chars"
|
|
124
|
+
for line in result.stderr.splitlines():
|
|
125
|
+
line = line.strip()
|
|
126
|
+
if line.startswith("[harnesstrim reduce]"):
|
|
127
|
+
rest = line[len("[harnesstrim reduce] "):]
|
|
128
|
+
if ":" in rest and "no reduction" not in rest:
|
|
129
|
+
reducer = rest.split(":")[0].strip()
|
|
130
|
+
return (result.stdout.rstrip("\n"), reducer)
|
|
131
|
+
except (FileNotFoundError, subprocess.TimeoutExpired, OSError):
|
|
132
|
+
pass
|
|
133
|
+
|
|
134
|
+
return (text, None)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def _text_targets(payload):
|
|
138
|
+
"""Yield mutable payload mappings and text-field names safe to reduce.
|
|
139
|
+
|
|
140
|
+
Hermes tools use different result schemas: terminal returns ``output``, file
|
|
141
|
+
tools ``content``, browser snapshots ``snapshot``, vision ``analysis``, and
|
|
142
|
+
web_extract has one content field per entry in ``results``.
|
|
143
|
+
"""
|
|
144
|
+
if not isinstance(payload, dict):
|
|
145
|
+
return []
|
|
146
|
+
|
|
147
|
+
targets = []
|
|
148
|
+
for key in ("output", "content", "snapshot", "analysis"):
|
|
149
|
+
if isinstance(payload.get(key), str) and payload[key]:
|
|
150
|
+
targets.append((payload, key))
|
|
151
|
+
break
|
|
152
|
+
|
|
153
|
+
results = payload.get("results")
|
|
154
|
+
if isinstance(results, list):
|
|
155
|
+
for entry in results:
|
|
156
|
+
if not isinstance(entry, dict):
|
|
157
|
+
continue
|
|
158
|
+
for key in ("content", "output", "text"):
|
|
159
|
+
if isinstance(entry.get(key), str) and entry[key]:
|
|
160
|
+
targets.append((entry, key))
|
|
161
|
+
break
|
|
162
|
+
return targets
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def on_tool_result(tool_name, args, result, **kwargs):
|
|
166
|
+
"""Reduce recognized textual fields while preserving the original JSON schema."""
|
|
167
|
+
cfg = _load_config()
|
|
168
|
+
if cfg["mode"] == "off" or tool_name not in REDUCER_TOOLS:
|
|
169
|
+
return None
|
|
170
|
+
if not result or not isinstance(result, str):
|
|
171
|
+
return None
|
|
172
|
+
|
|
173
|
+
try:
|
|
174
|
+
payload = json.loads(result)
|
|
175
|
+
except (json.JSONDecodeError, ValueError):
|
|
176
|
+
payload = None
|
|
177
|
+
|
|
178
|
+
if isinstance(payload, str):
|
|
179
|
+
targets = [(None, None, payload)]
|
|
180
|
+
elif payload is None:
|
|
181
|
+
targets = [(None, None, result)]
|
|
182
|
+
else:
|
|
183
|
+
targets = [(container, key, container[key]) for container, key in _text_targets(payload)]
|
|
184
|
+
|
|
185
|
+
if not targets:
|
|
186
|
+
return None
|
|
187
|
+
|
|
188
|
+
changed = False
|
|
189
|
+
for container, key, text in targets:
|
|
190
|
+
before_len = len(text)
|
|
191
|
+
if before_len < cfg["minLength"]:
|
|
192
|
+
continue
|
|
193
|
+
if "[harnesstrim:" in text or "[hermes-trim" in text:
|
|
194
|
+
continue
|
|
195
|
+
|
|
196
|
+
after, reducer = _call_reducer(text, cfg["minLength"])
|
|
197
|
+
if after == text:
|
|
198
|
+
continue
|
|
199
|
+
|
|
200
|
+
changed = True
|
|
201
|
+
if cfg["mode"] == "dryrun":
|
|
202
|
+
print(
|
|
203
|
+
f"[harnesstrim] dryrun: {tool_name} "
|
|
204
|
+
f"{before_len} -> {len(after)} chars (would save {before_len - len(after)})",
|
|
205
|
+
file=sys.stderr,
|
|
206
|
+
)
|
|
207
|
+
continue
|
|
208
|
+
|
|
209
|
+
if container is None:
|
|
210
|
+
payload = after
|
|
211
|
+
else:
|
|
212
|
+
container[key] = after
|
|
213
|
+
if cfg["telemetry"]:
|
|
214
|
+
_write_metric(tool_name, reducer, before_len, len(after))
|
|
215
|
+
|
|
216
|
+
if not changed or cfg["mode"] == "dryrun":
|
|
217
|
+
return None
|
|
218
|
+
return json.dumps(payload, ensure_ascii=False) if not isinstance(payload, str) else payload
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def _write_metric(tool: str, reducer: str | None, before: int, after: int) -> None:
|
|
222
|
+
"""Append one TrimEvent JSONL line to METRICS_PATH (read by `harnesstrim metrics`).
|
|
223
|
+
|
|
224
|
+
Only called in active mode when telemetry is explicitly enabled. Creates the parent
|
|
225
|
+
directory lazily and swallows any error — telemetry must never crash the plugin.
|
|
226
|
+
"""
|
|
227
|
+
import datetime as _dt
|
|
228
|
+
|
|
229
|
+
event = {
|
|
230
|
+
"ts": _dt.datetime.now(_dt.timezone.utc).isoformat(),
|
|
231
|
+
"harness": "hermes",
|
|
232
|
+
"tool": tool,
|
|
233
|
+
"reducer": reducer,
|
|
234
|
+
"beforeChars": before,
|
|
235
|
+
"afterChars": after,
|
|
236
|
+
}
|
|
237
|
+
try:
|
|
238
|
+
METRICS_PATH.parent.mkdir(parents=True, exist_ok=True)
|
|
239
|
+
with open(METRICS_PATH, "a", encoding="utf-8") as f:
|
|
240
|
+
f.write(json.dumps(event, ensure_ascii=False) + "\n")
|
|
241
|
+
except OSError:
|
|
242
|
+
pass # telemetry must never crash the plugin
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// HarnessTrim Pi extension — slims noisy tool output via the `tool_result` hook.
|
|
2
|
+
//
|
|
3
|
+
// Pi fires `tool_result` after a tool finishes and before the result reaches the model;
|
|
4
|
+
// handlers chain like middleware and may return a patch ({ content, details, isError }).
|
|
5
|
+
// This extension reduces string `content` for noisy output (test runners, git diffs, ...)
|
|
6
|
+
// by shelling out to `harnesstrim reduce`, so it is self-contained (no workspace imports)
|
|
7
|
+
// and loads from `~/.pi/agent/extensions/` or `<project>/.pi/extensions/`.
|
|
8
|
+
//
|
|
9
|
+
// Requires `harnesstrim` on PATH; if it is missing or fails, the output is passed through
|
|
10
|
+
// unchanged (a reducer must never break a tool result). Config via env:
|
|
11
|
+
// HARNESSTRIM_MODE=dryrun|active|off (default dryrun — logs, does not mutate)
|
|
12
|
+
// HARNESSTRIM_MINLENGTH=<chars> (default 400)
|
|
13
|
+
import { spawnSync } from "node:child_process";
|
|
14
|
+
|
|
15
|
+
interface ToolResultEvent {
|
|
16
|
+
content?: unknown;
|
|
17
|
+
isError?: boolean;
|
|
18
|
+
}
|
|
19
|
+
interface ExtensionAPI {
|
|
20
|
+
on(event: string, handler: (event: ToolResultEvent, ctx: unknown) => unknown): void;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const env = globalThis.process?.env ?? {};
|
|
24
|
+
const MODE = env.HARNESSTRIM_MODE ?? "dryrun";
|
|
25
|
+
const MIN_LENGTH = Number(env.HARNESSTRIM_MINLENGTH ?? "400") || 400;
|
|
26
|
+
const MARKER = "[harnesstrim";
|
|
27
|
+
|
|
28
|
+
function reduceViaCli(text: string): string | null {
|
|
29
|
+
try {
|
|
30
|
+
const r = spawnSync("harnesstrim", ["reduce", "--min-length", String(MIN_LENGTH)], {
|
|
31
|
+
input: text,
|
|
32
|
+
encoding: "utf8",
|
|
33
|
+
timeout: 30000,
|
|
34
|
+
});
|
|
35
|
+
if (r.status === 0 && typeof r.stdout === "string" && r.stdout.length > 0) {
|
|
36
|
+
return r.stdout.replace(/\n$/, "");
|
|
37
|
+
}
|
|
38
|
+
} catch {
|
|
39
|
+
/* harnesstrim not on PATH or failed — pass through */
|
|
40
|
+
}
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export default function harnesstrim(pi: ExtensionAPI): void {
|
|
45
|
+
if (MODE === "off") return;
|
|
46
|
+
pi.on("tool_result", async (event) => {
|
|
47
|
+
const content = event.content;
|
|
48
|
+
if (typeof content !== "string" || content.length < MIN_LENGTH) return;
|
|
49
|
+
if (content.includes(MARKER)) return; // already reduced — avoid double work
|
|
50
|
+
|
|
51
|
+
const reduced = reduceViaCli(content);
|
|
52
|
+
if (!reduced || reduced.length >= content.length) return;
|
|
53
|
+
|
|
54
|
+
if (MODE === "dryrun") {
|
|
55
|
+
globalThis.process?.stderr?.write(
|
|
56
|
+
`[harnesstrim] dryrun tool_result: ${content.length} -> ${reduced.length} chars\n`
|
|
57
|
+
);
|
|
58
|
+
return; // dryrun: observe only
|
|
59
|
+
}
|
|
60
|
+
return { content: reduced }; // active: patch the tool result the model sees
|
|
61
|
+
});
|
|
62
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: compact-handoff
|
|
3
|
+
description: This skill should be used when a session is being compacted/summarized (context is full or the user runs a compact command), or when writing any handoff summary that a later session or subagent will continue from. Defines what to preserve versus drop so the continuation doesn't waste tokens re-reading files and re-deriving state. Part of the HarnessTrim token-economy stack; the OpenCode adapter injects this guidance automatically via experimental.session.compacting.
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
license: MIT
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# compact-handoff
|
|
9
|
+
|
|
10
|
+
A compaction summary is the seed context for everything that follows. If it drops the wrong
|
|
11
|
+
things, the next turns re-read files, re-run searches, and re-decide settled questions — spending
|
|
12
|
+
far more tokens than the summary ever saved. Optimize the summary for *resumption*, not brevity.
|
|
13
|
+
|
|
14
|
+
## Preserve
|
|
15
|
+
|
|
16
|
+
1. **The task and its acceptance criteria** — what "done" means, verbatim if the user stated it.
|
|
17
|
+
2. **Decisions made and alternatives rejected** — with the reason, so they aren't re-litigated.
|
|
18
|
+
3. **Hard-won specifics** — exact file paths, symbol names, commands that worked, config values,
|
|
19
|
+
version numbers, API signatures discovered. These are the expensive-to-rediscover facts.
|
|
20
|
+
4. **Current state** — what is done, what is verified (and how), what is still failing and the
|
|
21
|
+
last error seen.
|
|
22
|
+
|
|
23
|
+
## Drop
|
|
24
|
+
|
|
25
|
+
- Step-by-step narration of what was already done ("first I opened X, then I ran Y").
|
|
26
|
+
- Raw tool output that has already been acted on (test logs, file dumps, search results).
|
|
27
|
+
- Anything trivially reconstructable from the current diff or a single cheap command.
|
|
28
|
+
- Restatements of the codebase's structure that the next session can read directly.
|
|
29
|
+
|
|
30
|
+
## Shape
|
|
31
|
+
|
|
32
|
+
Write the summary as short labeled sections (Task / Decisions / Key facts / State), not prose.
|
|
33
|
+
A resuming agent should be able to act from it without re-reading the whole transcript.
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: debug-log-slim
|
|
3
|
+
description: This skill should be used before pasting, printing, or otherwise embedding test-runner output, build logs, or CI output into the conversation — whenever a test suite, linter, or build was just run and its raw output is large. Filters logs down to failure/error signal before they enter context. Part of the HarnessTrim token-economy stack (see packages/core/src/reducers/test-output-slim.ts for the reference implementation the OpenCode adapter automates this with).
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
license: MIT
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# debug-log-slim
|
|
9
|
+
|
|
10
|
+
Raw test/build output is mostly noise: hundreds of PASS/OK lines for every one line that
|
|
11
|
+
actually matters. Don't paste it all into context — filter first.
|
|
12
|
+
|
|
13
|
+
## Rule
|
|
14
|
+
|
|
15
|
+
Before showing test/build/lint output to the user or reasoning over it at length, reduce it to:
|
|
16
|
+
|
|
17
|
+
1. **Every FAIL / ERROR / Exception / Traceback / assertion line.**
|
|
18
|
+
2. **A few lines of context around each** (the stack frame or expected/received diff — usually
|
|
19
|
+
3–5 lines is enough to act on).
|
|
20
|
+
3. **The final summary line** (e.g. `12 passed, 3 failed`), always kept verbatim.
|
|
21
|
+
4. **Everything else collapsed** into a single count, e.g. `… 40 passing lines omitted …` — never
|
|
22
|
+
silently dropped without saying how much was cut.
|
|
23
|
+
|
|
24
|
+
## How to apply it
|
|
25
|
+
|
|
26
|
+
- If the HarnessTrim OpenCode adapter is installed, this happens automatically via
|
|
27
|
+
`tool.execute.before` — no manual filtering needed, this skill is then just a fallback for
|
|
28
|
+
contexts where the adapter isn't wired in.
|
|
29
|
+
- Without the adapter: when a command produces long output, pipe it through a targeted filter
|
|
30
|
+
before reading it in full — e.g. `grep -E 'FAIL|ERROR|Exception' -A 5` (adjust the pattern to
|
|
31
|
+
the test runner's failure markers) — rather than reading the entire raw log.
|
|
32
|
+
- Never summarize-then-discard: if you filtered, the user should still be able to ask "show me
|
|
33
|
+
the full log" and get it — filtering is about what enters *reasoning context* by default, not
|
|
34
|
+
deleting the underlying artifact.
|
|
35
|
+
|
|
36
|
+
## What NOT to filter out
|
|
37
|
+
|
|
38
|
+
- Warnings that indicate a real (if non-fatal) problem — e.g. deprecation notices tied to the
|
|
39
|
+
change just made.
|
|
40
|
+
- Anything the user explicitly asked to see in full.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: delegate-bulk
|
|
3
|
+
description: This skill should be used when deciding whether to hand volumetric or noisy work to an isolated subagent (or another harness) instead of doing it in the main context — e.g. reading many files, digesting long logs/docs, wide searches, or bulk mechanical generation. It gives the rule for when isolation saves tokens versus when it just multiplies them. Part of the HarnessTrim token-economy stack.
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
license: MIT
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# delegate-bulk
|
|
9
|
+
|
|
10
|
+
Subagents are not free parallelism — each carries its own context and bills its own tokens. The
|
|
11
|
+
right move is surgical isolation of *noise*, not "multi-agent everything."
|
|
12
|
+
|
|
13
|
+
## Delegate to an isolated subagent when
|
|
14
|
+
|
|
15
|
+
- A task will **read a large volume the main thread doesn't need to retain** — many files, long
|
|
16
|
+
logs, big docs, wide search results — and you only need the *conclusion* back, not the raw bytes.
|
|
17
|
+
- The work is **noisy but self-contained**: exploring a subsystem, triaging a failing test suite,
|
|
18
|
+
digesting API docs. Isolate it so its raw output never pollutes the main context.
|
|
19
|
+
- You need **several independent investigations at once** and each one's detail is irrelevant to the
|
|
20
|
+
others.
|
|
21
|
+
|
|
22
|
+
The token win comes from the subagent returning a short structured answer while its expensive
|
|
23
|
+
reading stays in a context you throw away.
|
|
24
|
+
|
|
25
|
+
## Do NOT delegate when
|
|
26
|
+
|
|
27
|
+
- The task is small enough that spawning a context costs more than it saves.
|
|
28
|
+
- The main thread needs the **full detail** anyway (delegating then re-reading is pure overhead).
|
|
29
|
+
- You'd spawn many agents that each re-load the same large context — that multiplies tokens, the
|
|
30
|
+
opposite of the goal.
|
|
31
|
+
|
|
32
|
+
## Cross-harness delegation
|
|
33
|
+
|
|
34
|
+
The same logic extends across harnesses: route bulk scaffolding/boilerplate to a cheaper or
|
|
35
|
+
execution-oriented channel and keep architecture, review, and validation on the main one. Suppress
|
|
36
|
+
the delegated channel's verbose thinking/output from re-entering the main context. Pair with
|
|
37
|
+
[[scaffold-fast]] for the delegated bulk work and [[compact-handoff]] for what the subagent returns.
|
|
38
|
+
|
|
39
|
+
## Rule of thumb
|
|
40
|
+
|
|
41
|
+
Delegate to **shrink** what the main context must hold, never to **duplicate** it.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: delta-response
|
|
3
|
+
description: This skill should be used for every response produced in a coding harness (Claude Code, Codex, OpenCode, Pi) — not only when explicitly requested. Enforces terse, structured, information-dense output instead of narrated verbosity, to cut output-token cost. Part of the HarnessTrim token-economy stack (see delta-response/references/examples.md for before/after samples).
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
license: MIT
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# delta-response
|
|
9
|
+
|
|
10
|
+
Say the delta, not the story. Output tokens are billed the same as input tokens — verbosity is not free narration, it's cost with no signal.
|
|
11
|
+
|
|
12
|
+
## Rules
|
|
13
|
+
|
|
14
|
+
1. **Lead with the result.** State the answer, the change, or the finding first. Don't restate the request or narrate intent ("I will now check...", "Let me look at...") before acting — just act, then report what you found.
|
|
15
|
+
2. **No filler.** Cut greetings, hedges, and meta-commentary ("Great question!", "As you can see,", "I hope this helps"). Every sentence must carry information the reader doesn't already have.
|
|
16
|
+
3. **Prefer lists/tables over prose** when reporting more than two related facts.
|
|
17
|
+
4. **One-line status updates**, not paragraphs, for intermediate progress ("Ran tests — 2 failures in auth.spec.ts", not three sentences describing the act of running tests).
|
|
18
|
+
5. **End-of-task summary: 1–2 sentences max** — what changed, what's next. No recap of steps already visible in the transcript/diff.
|
|
19
|
+
6. **Don't over-explain obvious code.** Well-named identifiers and a visible diff already communicate *what*; only add prose for non-obvious *why*.
|
|
20
|
+
7. **Match length to the question.** A yes/no question gets a direct answer, not headers and sections.
|
|
21
|
+
|
|
22
|
+
## When NOT to compress
|
|
23
|
+
|
|
24
|
+
Don't cut content that changes a decision: trade-offs the user must choose between, a warning about a risky/irreversible action, or a caveat that changes whether the result can be trusted. Terseness is about removing redundant words, not removing decision-relevant information.
|
|
25
|
+
|
|
26
|
+
See `references/examples.md` for verbose-vs-terse rewrites.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# delta-response — before/after examples
|
|
2
|
+
|
|
3
|
+
**Task: "run the tests"**
|
|
4
|
+
|
|
5
|
+
Verbose (avoid):
|
|
6
|
+
> I'll go ahead and run the test suite now to check if everything is passing correctly.
|
|
7
|
+
> [runs tests]
|
|
8
|
+
> Great, I've run the tests for you! Here's what I found: the test suite completed and it
|
|
9
|
+
> looks like there were a couple of failures. Let me break those down for you in detail...
|
|
10
|
+
|
|
11
|
+
Terse (target):
|
|
12
|
+
> Ran tests — 2 failures: `auth.spec.ts:42` (timeout), `billing.spec.ts:110` (assertion mismatch).
|
|
13
|
+
|
|
14
|
+
**Task: "what does this function do?"**
|
|
15
|
+
|
|
16
|
+
Verbose (avoid):
|
|
17
|
+
> So looking at this function, what we can see here is that it takes an input parameter
|
|
18
|
+
> called `items`, and then what it does is it iterates over each item in the array using
|
|
19
|
+
> a for loop, and for each one it checks a condition...
|
|
20
|
+
|
|
21
|
+
Terse (target):
|
|
22
|
+
> Filters `items` to those with `active: true`, then sorts by `createdAt` descending.
|
|
23
|
+
|
|
24
|
+
**Task: "fix the bug and tell me what you did"**
|
|
25
|
+
|
|
26
|
+
Verbose (avoid):
|
|
27
|
+
> I identified the root cause of the issue. The problem was in the validation logic.
|
|
28
|
+
> I've now updated the code to fix this. Here's a summary of the changes I made: first,
|
|
29
|
+
> I changed the regex pattern, then I added a null check, and finally I updated the tests
|
|
30
|
+
> to reflect these changes. Let me know if you have any questions!
|
|
31
|
+
|
|
32
|
+
Terse (target):
|
|
33
|
+
> Fixed: regex in `validate.ts:18` didn't allow `+` in emails; added a null guard for empty input. Tests updated.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: review-delta
|
|
3
|
+
description: This skill should be used when asked to review code, review a pull request, or review a diff. Focuses the review on problems worth fixing rather than restating or summarizing what changed. Part of the HarnessTrim token-economy stack.
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
license: MIT
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# review-delta
|
|
9
|
+
|
|
10
|
+
A code review that restates the diff wastes tokens telling the reader something the diff already
|
|
11
|
+
shows. Review for *problems*, not for *content*.
|
|
12
|
+
|
|
13
|
+
## Rules
|
|
14
|
+
|
|
15
|
+
1. **Don't summarize what changed file-by-file.** The diff is already visible; narrating
|
|
16
|
+
"this file now does X, that file now does Y" adds no information.
|
|
17
|
+
2. **Only report actual findings**: correctness bugs, security issues, missed edge cases,
|
|
18
|
+
inconsistent-with-the-rest-of-the-codebase patterns, risky assumptions. If a file has nothing
|
|
19
|
+
wrong, say nothing about it — don't write "looks good" for every clean file.
|
|
20
|
+
3. **Rank findings by severity**, most important first.
|
|
21
|
+
4. **Cite `file:line`** for every finding so it's actionable, not abstract.
|
|
22
|
+
5. **One or two sentences per finding**: what's wrong and the concrete failure scenario
|
|
23
|
+
(input/state that breaks), plus a fix suggestion only if it's non-obvious.
|
|
24
|
+
6. **If there are zero findings, say so in one line.** Don't pad a clean review to look thorough.
|
|
25
|
+
|
|
26
|
+
## Failure scenario, not just a label
|
|
27
|
+
|
|
28
|
+
"This could be a null pointer issue" is not a finding. "`user` can be `undefined` when
|
|
29
|
+
`fetchUser` 404s (see `api.ts:12`), and `line 40` dereferences it without a check — crashes on
|
|
30
|
+
any unknown user id" is a finding: concrete state, concrete break.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: scaffold-fast
|
|
3
|
+
description: This skill should be used for mechanical, low-novelty coding work — generating boilerplate, wiring up a component from an existing pattern, adding a CRUD endpoint like the others, writing repetitive tests, or applying a rote transformation across files. It keeps reasoning effort low and output terse for work whose shape is already decided. Part of the HarnessTrim token-economy stack; the lean-scaffold preset pairs it with low reasoning effort.
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
license: MIT
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# scaffold-fast
|
|
9
|
+
|
|
10
|
+
When the *shape* of the work is already decided and the task is to produce more of it, spend tokens
|
|
11
|
+
on the code, not on deliberation. Deep reasoning on rote work is wasted budget.
|
|
12
|
+
|
|
13
|
+
## When this applies
|
|
14
|
+
|
|
15
|
+
- Boilerplate: config files, DTOs, barrel exports, index files.
|
|
16
|
+
- "Same as the others" work: a new endpoint/component/model that mirrors existing ones.
|
|
17
|
+
- Repetitive tests following an established pattern.
|
|
18
|
+
- Mechanical transforms: rename a symbol across files, migrate a call signature, reformat.
|
|
19
|
+
|
|
20
|
+
## Rules
|
|
21
|
+
|
|
22
|
+
1. **Copy the existing pattern, don't reinvent it.** Find the nearest sibling (the last component,
|
|
23
|
+
the adjacent endpoint) and match its structure, naming, imports, and error handling exactly.
|
|
24
|
+
2. **Minimal reasoning.** Don't weigh architectural alternatives for work whose design is settled —
|
|
25
|
+
if you find yourself deliberating, that's a signal the task is *not* scaffolding and this skill
|
|
26
|
+
doesn't apply.
|
|
27
|
+
3. **Terse output.** State what was generated in one line; the diff shows the rest. No walkthrough.
|
|
28
|
+
4. **Don't gold-plate.** No speculative abstraction, options, or config for needs nobody stated.
|
|
29
|
+
5. **Batch mechanical edits** rather than narrating each one.
|
|
30
|
+
|
|
31
|
+
## When to STOP and escalate
|
|
32
|
+
|
|
33
|
+
If the "boilerplate" turns out to require a real decision — an unclear data model, a security
|
|
34
|
+
boundary, an ambiguous requirement — stop scaffolding and surface the decision. Fast is for settled
|
|
35
|
+
work, not for guessing past ambiguity. See [[delegate-bulk]] when the volume itself is the problem.
|