teapot-coding-agent 0.37.103 → 0.37.104

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.
@@ -78,6 +78,11 @@ const SUMMARY_CLOSE = "\n</conversation_checkpoint>";
78
78
  const AUDIT_READ_ONLY_TOOLS = new Set(["read_file", "list_dir", "grep", "bash"]);
79
79
  const SYSTEM_TEMPLATE = `You are a coding agent working autonomously inside a workspace.
80
80
 
81
+ Start any session with three cheap calls, in this order: get_goal() (what am I
82
+ actually doing), get_todo() (what the operator is tracking), list_skills() (what
83
+ playbooks already exist). They cost about a line each and decide what to do next;
84
+ skipping them is how work already done gets done again.
85
+
81
86
  Most session state is not injected into prompts — fetch it with tools instead:
82
87
  - get_goal() → current objective + status. Call at session start, after a
83
88
  compaction notice, or whenever you lose the thread.
@@ -85,6 +90,9 @@ Most session state is not injected into prompts — fetch it with tools instead:
85
90
  - finish(goalComplete=true, summary) → goal fully achieved.
86
91
  - ask_user(question, options?) → park the loop and wait for the operator's
87
92
  decision (plan confirmation, ambiguity). One concrete question at a time.
93
+ Which one ends the turn: finish() closes the round, ask_user() parks it until
94
+ the operator answers, and report_progress() does NOT end it — that output goes
95
+ to the operator's progress panel and you carry on working.
88
96
  - read_memory(query?) / add_memory(text) / forget_memory(query) / set_memory(content)
89
97
  → your durable notes (memory.md). add_memory appends and cannot lose earlier
90
98
  notes; forget_memory removes the lines matching a substring, so a stale note can
@@ -117,17 +125,19 @@ Most session state is not injected into prompts — fetch it with tools instead:
117
125
  valid string), and it took longer to repair than doing the edits one by one.
118
126
  A genuine bulk transform across many files (a rename, an import change) IS a
119
127
  job for a script — then keep the pattern specific and read the diff.
120
- - **NEVER \`sleep\` to wait for anything.** Not alone, not as "a quick poll".
121
- A bash call with a sleep inside is one opaque command: the operator cannot
122
- interrupt it, cannot see whether anything is happening, and the whole turn is
123
- spent doing nothing. Two interruptible shapes instead:
128
+ - **Never wait with a bare sleep.** A bash call with a sleep inside is one opaque
129
+ command: the operator cannot interrupt it, cannot see whether anything is
130
+ happening, and the whole turn is spent doing nothing. A fixed nap also cannot
131
+ tell "finished 5 minutes ago" from "still running", which is how a turn gets
132
+ wasted twice. Two interruptible shapes instead:
124
133
  · long single-purpose work (CI, a release, a tarball appearing, a dev
125
134
  server, a watcher) → bash(background=true). It returns a job id at once
126
135
  and you are NOTIFIED when it exits, so keep working and let it report.
127
- · waiting on a condition you are actively watching → poll with a bounded
128
- deadline in ONE call, then stop.
129
- A fixed wait also cannot tell "finished 5 minutes ago" from "still running",
130
- which is how a turn gets wasted twice.
136
+ · waiting on a condition you are actively watching → poll for the CONDITION
137
+ with a bounded deadline in ONE call, then stop. Polling is not a nap: it
138
+ re-checks and returns as soon as the condition holds.
139
+ The line is fixed-vs-condition, not short-vs-long: a bounded poll that returns
140
+ on the condition is fine, a sleep that always burns the same wall-clock is not.
131
141
  - When a loaded skill matches your task, follow its playbook.
132
142
  - Turn proven procedures into skills: once something non-trivial worked well,
133
143
  save_skill(name, description, content, files=[{name, content}]) so future
@@ -149,6 +159,11 @@ Most session state is not injected into prompts — fetch it with tools instead:
149
159
  panel) — do NOT also repeat the same content as a chat message; end the
150
160
  turn right after the call (a short "reported." is fine).
151
161
  - Be frugal: prefer small precise edits, avoid runaway loops.
162
+ - **Two failures on the same cause is the signal to change approach, not to try
163
+ a third time.** Reread the error, list what you have already ruled out, and
164
+ then either fix the actual cause or ask_user with what you know: two concrete
165
+ options and the evidence for each. Grinding a third variation of a failing idea
166
+ is the costliest loop there is, because it looks like progress.
152
167
  - When the operator asks you to remember something, WRITE it with add_memory.
153
168
  Write the KIND of thing that stays true and saves work later: a decision and
154
169
  why, a gotcha that cost time, a command that works here, where you left off.
@@ -164,6 +179,8 @@ Most session state is not injected into prompts — fetch it with tools instead:
164
179
  read_memory(query), open at most one or two of the notes that match, and stop.
165
180
  If nothing is relevant, carry on — do not keep searching, and do not read the
166
181
  whole file "just in case". An unread note is an unwritten note.`;
182
+ /** how much of memory.md a no-query read_memory() may dump into the transcript */
183
+ export const MEMORY_DUMP_LIMIT = 8_000;
167
184
  export const PLACEHOLDER_TEXT = /^\(.*\)$/;
168
185
  export const HARNESS_ACK = /^(decision recorded|progress recorded|goal saved|feedback saved|memory saved|todo saved|no skills yet|skills created|read_memory|memory|\(tool call\)|\(no content\)|\(no output\)|true|false|null|none)\b/i;
169
186
  export class Agent {
@@ -1660,7 +1677,16 @@ export class Agent {
1660
1677
  return "next";
1661
1678
  }
1662
1679
  if (!query) {
1663
- await this.answerMeta(call, mem.trim());
1680
+ /**
1681
+ * No query means "give me everything", and everything is now tens of KB — memory.md is capped at
1682
+ * 32_000 chars and a long-lived session fills it. Dumping that into the transcript costs more than
1683
+ * the answer is worth, and the prompt already carries a digest, so cap it and say how to narrow.
1684
+ */
1685
+ const body = mem.trim();
1686
+ await this.answerMeta(call, body.length <= MEMORY_DUMP_LIMIT
1687
+ ? body
1688
+ : `${body.slice(0, MEMORY_DUMP_LIMIT)}\n… (truncated — ${body.length} chars in memory.md; ` +
1689
+ `pass a query to read a specific part)`);
1664
1690
  return "next";
1665
1691
  }
1666
1692
  const needle = query.toLowerCase();
@@ -1459,12 +1459,8 @@ export const TOOLS = [
1459
1459
  const id = startBackgroundShell(str(args.command), ctx);
1460
1460
  return {
1461
1461
  ok: true,
1462
- result: `started in background (job ${id}). It runs while you work — you will be notified ` +
1463
- `here when it exits (exit code + output tail), and the round WAITS for that rather ` +
1464
- `than ending. Do NOT poll bash_output: each poll is a whole turn, and the report ` +
1465
- `already carries the tail. Call bash_output only when you need output BEYOND the ` +
1466
- `tail (a long build's earlier lines), or to kill it. ` +
1467
- `Full output: bash_output(job_id="${id}"). Kill: bash_output(job_id="${id}", action="kill").`,
1462
+ result: `started in background (job ${id}) — you are notified when it exits (code + output tail); ` +
1463
+ `do NOT poll it. Output: bash_output(job_id="${id}"). Stop: bash_output(job_id="${id}", action="kill").`,
1468
1464
  };
1469
1465
  }
1470
1466
  const explicit = args.timeout_ms !== undefined && args.timeout_ms !== null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "teapot-coding-agent",
3
- "version": "0.37.103",
3
+ "version": "0.37.104",
4
4
  "description": "A lightweight, always-on multi-agent harness for AI coding agents",
5
5
  "type": "module",
6
6
  "license": "AGPL-3.0-or-later",