teapot-coding-agent 0.6.0 → 0.7.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/README.md +13 -7
- package/dist/agent/agent.js +77 -61
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -110,18 +110,24 @@ master (Hono server, src/master.ts + src/server/api.ts)
|
|
|
110
110
|
never hard-coded.
|
|
111
111
|
- **Tools** (`src/agent/tools.ts`): provider-agnostic JSON-schema function
|
|
112
112
|
specs — `read_file`, `write_file`, `edit_file`, `list_dir`, `bash` (git goes
|
|
113
|
-
through bash), plus meta tools `finish` / `report_progress` / `
|
|
114
|
-
`
|
|
115
|
-
|
|
113
|
+
through bash), plus meta tools `finish` / `report_progress` / `get_goal` /
|
|
114
|
+
`set_goal` / `read_memory` / `set_memory` / `list_skills`. Paths are
|
|
115
|
+
confined to the workspace; bash runs detached in its own process group and
|
|
116
|
+
the whole group is SIGKILLed on timeout.
|
|
117
|
+
- **Cache-friendly prompt design** — the system prompt is byte-identical on
|
|
118
|
+
every turn; session state (goal, memory, skills) is fetched via tools, never
|
|
119
|
+
injected. Combined with the append-only message history this keeps provider
|
|
120
|
+
prefix caches hot, so long sessions pay incremental input prices instead of
|
|
121
|
+
re-sending full context every turn.
|
|
116
122
|
- **Session storage** — everything teapot manages lives under
|
|
117
123
|
`<dataDir>/sessions/<sid>/` (`chat.jsonl`, `goal.md`, `memory.md`), so agent
|
|
118
124
|
workspaces stay clean. Each incarnation gets a fresh `<agentId>-<uuid>`
|
|
119
125
|
directory (no history leaks across projects); restarts reuse the latest one.
|
|
120
126
|
Legacy layouts are migrated automatically.
|
|
121
|
-
- **Goal / knowledge** — goal + memory are harness-managed and
|
|
122
|
-
|
|
123
|
-
`AGENTS.md` is optional project knowledge in the workspace root that
|
|
124
|
-
|
|
127
|
+
- **Goal / knowledge** — goal + memory are harness-managed and read/written
|
|
128
|
+
through tools (`get_goal` / `set_goal` / `read_memory` / `set_memory`);
|
|
129
|
+
`AGENTS.md` is optional project knowledge in the workspace root that agents
|
|
130
|
+
are told to read at session start. Nothing is seeded into your project.
|
|
125
131
|
|
|
126
132
|
### Web UI
|
|
127
133
|
|
package/dist/agent/agent.js
CHANGED
|
@@ -14,24 +14,32 @@ import { executeTool, toolSpecs, currentSkills } from "./tools.js";
|
|
|
14
14
|
import { bus } from "../bus.js";
|
|
15
15
|
const SYSTEM_TEMPLATE = `You are a coding agent working autonomously inside a workspace.
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
only when the goal is fully achieved.
|
|
17
|
+
This system prompt is intentionally STATIC — session state is never injected
|
|
18
|
+
into it, so API prompt caches stay hot across turns. Fetch state with tools
|
|
19
|
+
instead; they are cheap and always current.
|
|
21
20
|
|
|
22
|
-
##
|
|
23
|
-
-
|
|
24
|
-
|
|
25
|
-
-
|
|
26
|
-
|
|
27
|
-
|
|
21
|
+
## Goal (harness-managed)
|
|
22
|
+
- get_goal() → current objective + status. Call it at session start, after a
|
|
23
|
+
compaction notice, or whenever you lose the thread.
|
|
24
|
+
- set_goal(text) → change the objective itself (not routine updates).
|
|
25
|
+
- finish(goalComplete=true, summary) → goal fully achieved.
|
|
26
|
+
|
|
27
|
+
## Your notes (memory.md, harness-managed)
|
|
28
|
+
- read_memory() / set_memory(content) → durable notes injected nowhere else.
|
|
29
|
+
Keep them terse: decisions, gotchas, where you left off.
|
|
30
|
+
|
|
31
|
+
## Skills (reusable playbooks)
|
|
32
|
+
- list_skills() → what exists. load_skill(name) → full playbook.
|
|
33
|
+
- save_skill(name, description, content) → distill a reusable procedure.
|
|
34
|
+
|
|
35
|
+
## Project knowledge
|
|
36
|
+
- AGENTS.md in the workspace root (optional): conventions & commands. Read it
|
|
37
|
+
with read_file at session start when present, keep it current.
|
|
28
38
|
|
|
29
39
|
## Rules
|
|
30
40
|
- Work step by step with tools. Verify results (run tests/builds) before claiming progress.
|
|
31
|
-
- When a
|
|
32
|
-
- When you develop a reusable procedure, save_skill it — skills persist and are offered to future sessions.
|
|
41
|
+
- When a loaded skill matches your task, follow its playbook.
|
|
33
42
|
- When you make meaningful progress, call report_progress.
|
|
34
|
-
- When the goal is fully achieved, call finish(goalComplete=true) with a short summary.
|
|
35
43
|
- Be frugal: prefer small precise edits, avoid runaway loops.`;
|
|
36
44
|
export class Agent {
|
|
37
45
|
log;
|
|
@@ -97,17 +105,6 @@ export class Agent {
|
|
|
97
105
|
/* keep previous cache */
|
|
98
106
|
}
|
|
99
107
|
}
|
|
100
|
-
skillsListing() {
|
|
101
|
-
if (this.skillsCache.length === 0) {
|
|
102
|
-
return ("## Skills\n" +
|
|
103
|
-
"No skills exist yet. When you develop a reusable procedure worth keeping " +
|
|
104
|
-
"(build steps, checklists, project conventions), distill it into a durable playbook " +
|
|
105
|
-
"with save_skill so future sessions can load it via load_skill.");
|
|
106
|
-
}
|
|
107
|
-
return ("## Skills (reusable playbooks)\n" +
|
|
108
|
-
"When the current task matches a description below, call load_skill(name) first and follow it.\n" +
|
|
109
|
-
this.skillsCache.map((s) => `- ${s.name}: ${s.description || "(no description)"}`).join("\n"));
|
|
110
|
-
}
|
|
111
108
|
callLlm(messages, tools, onDelta) {
|
|
112
109
|
const fn = this.opts.chatFn ?? chatStream;
|
|
113
110
|
return fn(this.opts.llm, messages, tools, this.abort?.signal, onDelta);
|
|
@@ -479,7 +476,7 @@ export class Agent {
|
|
|
479
476
|
turn: ++this.stats.turns,
|
|
480
477
|
});
|
|
481
478
|
// stream the assistant reply live to connected clients
|
|
482
|
-
const res = await this.llmCall(
|
|
479
|
+
const res = await this.llmCall(this.buildMessages(), allToolSpecs(), (s) => {
|
|
483
480
|
bus.emit("update", {
|
|
484
481
|
kind: "llm-delta",
|
|
485
482
|
agentId: this.opts.id,
|
|
@@ -537,6 +534,31 @@ export class Agent {
|
|
|
537
534
|
});
|
|
538
535
|
continue;
|
|
539
536
|
}
|
|
537
|
+
if (call.function.name === "get_goal") {
|
|
538
|
+
this.messages.push({
|
|
539
|
+
role: "tool",
|
|
540
|
+
tool_call_id: call.id,
|
|
541
|
+
content: JSON.stringify({ goal: this.goal.text || "(none set)", status: this.goal.status }, null, 1),
|
|
542
|
+
});
|
|
543
|
+
continue;
|
|
544
|
+
}
|
|
545
|
+
if (call.function.name === "read_memory") {
|
|
546
|
+
const mem = await fs.readFile(this.memoryFile, "utf8").catch(() => "");
|
|
547
|
+
this.messages.push({
|
|
548
|
+
role: "tool",
|
|
549
|
+
tool_call_id: call.id,
|
|
550
|
+
content: mem.trim() || "(memory.md is empty — nothing noted yet)",
|
|
551
|
+
});
|
|
552
|
+
continue;
|
|
553
|
+
}
|
|
554
|
+
if (call.function.name === "list_skills") {
|
|
555
|
+
await this.refreshSkills();
|
|
556
|
+
const list = this.skillsCache.length
|
|
557
|
+
? this.skillsCache.map((s) => `- ${s.name}: ${s.description || "(no description)"}`).join("\n")
|
|
558
|
+
: "(no skills yet — create one with save_skill)";
|
|
559
|
+
this.messages.push({ role: "tool", tool_call_id: call.id, content: list });
|
|
560
|
+
continue;
|
|
561
|
+
}
|
|
540
562
|
if (call.function.name === "set_memory") {
|
|
541
563
|
const a = safeParse(call.function.arguments);
|
|
542
564
|
const content = String(a.content ?? "").slice(0, 32_000);
|
|
@@ -574,37 +596,10 @@ export class Agent {
|
|
|
574
596
|
}
|
|
575
597
|
throw new Error("runaway detection: too many turns in one round (>200)");
|
|
576
598
|
}
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
async readAgentsMd() {
|
|
580
|
-
try {
|
|
581
|
-
const p = path.join(this.workspace, "AGENTS.md");
|
|
582
|
-
const st = await fs.stat(p);
|
|
583
|
-
if (this.agentsMdCache?.mtimeMs === st.mtimeMs)
|
|
584
|
-
return this.agentsMdCache.text;
|
|
585
|
-
const text = await fs.readFile(p, "utf8");
|
|
586
|
-
this.agentsMdCache = { mtimeMs: st.mtimeMs, text };
|
|
587
|
-
return text;
|
|
588
|
-
}
|
|
589
|
-
catch {
|
|
590
|
-
this.agentsMdCache = null;
|
|
591
|
-
return "";
|
|
592
|
-
}
|
|
593
|
-
}
|
|
594
|
-
async buildMessages() {
|
|
595
|
-
const sys = [SYSTEM_TEMPLATE];
|
|
596
|
-
if (this.goal.text) {
|
|
597
|
-
sys.push(`## Current goal (${this.goal.status})\n${this.goal.text}`);
|
|
598
|
-
}
|
|
599
|
-
const agentsMd = await this.readAgentsMd();
|
|
600
|
-
if (agentsMd.trim())
|
|
601
|
-
sys.push(`## Project knowledge (AGENTS.md)\n${clipText(agentsMd, 8000)}`);
|
|
602
|
-
const memory = await fs.readFile(this.memoryFile, "utf8").catch(() => "");
|
|
603
|
-
if (memory.trim())
|
|
604
|
-
sys.push(`## Your notes (memory.md)\n${clipText(memory, 4000)}`);
|
|
605
|
-
sys.push(this.skillsListing());
|
|
599
|
+
buildMessages() {
|
|
600
|
+
// deliberately static: [system] + append-only history keeps prefix caches hot
|
|
606
601
|
const hasSystem = this.messages[0]?.role === "system";
|
|
607
|
-
const head = [{ role: "system", content:
|
|
602
|
+
const head = [{ role: "system", content: SYSTEM_TEMPLATE }];
|
|
608
603
|
return hasSystem ? [...head, ...this.messages.slice(1)] : [...head, ...this.messages];
|
|
609
604
|
}
|
|
610
605
|
async handleFinish(argsJson) {
|
|
@@ -629,7 +624,7 @@ export class Agent {
|
|
|
629
624
|
text: request,
|
|
630
625
|
});
|
|
631
626
|
this.messages.push({ role: "user", content: request });
|
|
632
|
-
const res = await this.llmCall(
|
|
627
|
+
const res = await this.llmCall(this.buildMessages(), []); // no tools: pure report
|
|
633
628
|
await this.recordProgress(JSON.stringify({ freeform: res.message.content }));
|
|
634
629
|
await this.log.append("message", this.currentSession, this.currentBranch, {
|
|
635
630
|
role: "assistant",
|
|
@@ -843,7 +838,7 @@ function allToolSpecs() {
|
|
|
843
838
|
type: "function",
|
|
844
839
|
function: {
|
|
845
840
|
name: "set_goal",
|
|
846
|
-
description: "Replace the harness-managed goal text
|
|
841
|
+
description: "Replace the harness-managed goal text. Use when the objective itself changes — not for routine updates.",
|
|
847
842
|
parameters: {
|
|
848
843
|
type: "object",
|
|
849
844
|
properties: { text: { type: "string" } },
|
|
@@ -851,11 +846,27 @@ function allToolSpecs() {
|
|
|
851
846
|
},
|
|
852
847
|
},
|
|
853
848
|
},
|
|
849
|
+
{
|
|
850
|
+
type: "function",
|
|
851
|
+
function: {
|
|
852
|
+
name: "get_goal",
|
|
853
|
+
description: "Fetch the current goal and its status. Cheap — call at session start, after a compaction notice, or when unsure.",
|
|
854
|
+
parameters: { type: "object", properties: {} },
|
|
855
|
+
},
|
|
856
|
+
},
|
|
857
|
+
{
|
|
858
|
+
type: "function",
|
|
859
|
+
function: {
|
|
860
|
+
name: "read_memory",
|
|
861
|
+
description: "Read your durable notes (memory.md).",
|
|
862
|
+
parameters: { type: "object", properties: {} },
|
|
863
|
+
},
|
|
864
|
+
},
|
|
854
865
|
{
|
|
855
866
|
type: "function",
|
|
856
867
|
function: {
|
|
857
868
|
name: "set_memory",
|
|
858
|
-
description: "Overwrite your durable
|
|
869
|
+
description: "Overwrite your durable notes (memory.md). Keep them terse: decisions, gotchas, where you left off.",
|
|
859
870
|
parameters: {
|
|
860
871
|
type: "object",
|
|
861
872
|
properties: { content: { type: "string" } },
|
|
@@ -863,11 +874,16 @@ function allToolSpecs() {
|
|
|
863
874
|
},
|
|
864
875
|
},
|
|
865
876
|
},
|
|
877
|
+
{
|
|
878
|
+
type: "function",
|
|
879
|
+
function: {
|
|
880
|
+
name: "list_skills",
|
|
881
|
+
description: "List available skills (name + description). Call before load_skill or to avoid duplicating an existing skill.",
|
|
882
|
+
parameters: { type: "object", properties: {} },
|
|
883
|
+
},
|
|
884
|
+
},
|
|
866
885
|
];
|
|
867
886
|
}
|
|
868
|
-
function clipText(s, max) {
|
|
869
|
-
return s.length <= max ? s : s.slice(0, max) + `\n… [truncated, ${s.length} chars total]`;
|
|
870
|
-
}
|
|
871
887
|
function str(v) {
|
|
872
888
|
return typeof v === "string" ? v : "";
|
|
873
889
|
}
|