teapot-coding-agent 0.5.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 CHANGED
@@ -110,13 +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`. Paths are
113
+ through bash), plus meta tools `finish` / `report_progress` / `get_goal` /
114
+ `set_goal` / `read_memory` / `set_memory` / `list_skills`. Paths are
114
115
  confined to the workspace; bash runs detached in its own process group and
115
116
  the whole group is SIGKILLed on timeout.
116
- - **Goal / knowledge**: `GOAL.md` (goal + status), `AGENTS.md` (project
117
- knowledge), `MEMORY.md` (agent notes) live in each workspace as normal git-
118
- editable Markdown. The goal file is the source of truth; the harness re-reads
119
- it on restart.
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.
122
+ - **Session storage** — everything teapot manages lives under
123
+ `<dataDir>/sessions/<sid>/` (`chat.jsonl`, `goal.md`, `memory.md`), so agent
124
+ workspaces stay clean. Each incarnation gets a fresh `<agentId>-<uuid>`
125
+ directory (no history leaks across projects); restarts reuse the latest one.
126
+ Legacy layouts are migrated automatically.
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.
120
131
 
121
132
  ### Web UI
122
133
 
@@ -162,13 +173,14 @@ description: Steps to cut a release safely
162
173
 
163
174
  ### Session log format (JSONL)
164
175
 
165
- One file per agent (`dataDir/<agent>.jsonl`); every conversation *including
166
- forks* lives in the same interleaved stream:
176
+ One file per session (`<dataDir>/sessions/<sid>/chat.jsonl`, sid =
177
+ `<agentId>-<uuid8>`); every conversation *including forks* lives in the same
178
+ interleaved stream:
167
179
 
168
180
  ```json
169
- {"v":1,"id":"e27","seq":27,"ts":"…","agent":"alpha","session":"sess-alpha-main",
181
+ {"v":1,"id":"e27","seq":27,"ts":"…","agent":"alpha","session":"alpha-9f3c21ab",
170
182
  "branch":"br032sl","parent":"e26","type":"fork",
171
- "data":{"fromSession":"sess-alpha-main","fromBranch":"br0","fromEvent":"e26","newBranch":"br032sl"}}
183
+ "data":{"fromSession":"alpha-9f3c21ab","fromBranch":"br0","fromEvent":"e26","newBranch":"br032sl"}}
172
184
  ```
173
185
 
174
186
  - every event carries `session`, `branch`, `parent` (previous event on the same branch), monotonic `seq`
@@ -14,19 +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
- ## Persistent context files (human-readable, git-tracked)
18
- - AGENTS.md : project knowledge/conventions written for agents (read it first)
19
- - GOAL.md : your current long-term goal and its status
20
- - MEMORY.md : durable notes you write for yourself
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
- Keep these files updated with edit_file/write_file. They survive restarts.
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.
23
38
 
24
39
  ## Rules
25
40
  - Work step by step with tools. Verify results (run tests/builds) before claiming progress.
26
- - When a task matches an available skill's description, load_skill it first and follow the playbook.
27
- - 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.
28
42
  - When you make meaningful progress, call report_progress.
29
- - When the goal is fully achieved, call finish(goalComplete=true) with a short summary.
30
43
  - Be frugal: prefer small precise edits, avoid runaway loops.`;
31
44
  export class Agent {
32
45
  log;
@@ -66,7 +79,7 @@ export class Agent {
66
79
  provider: "",
67
80
  ...opts,
68
81
  };
69
- this.log = new EventLog(opts.logFile, opts.id);
82
+ this.log = new EventLog(path.join(opts.sessionDir, "chat.jsonl"), opts.id);
70
83
  this.skillRoots = [
71
84
  { dir: path.join(opts.workspace, "skills"), source: "workspace" },
72
85
  ...(opts.globalSkillsDir ? [{ dir: opts.globalSkillsDir, source: "global" }] : []),
@@ -77,7 +90,8 @@ export class Agent {
77
90
  maxOutputBytes: 60_000,
78
91
  skillRoots: this.skillRoots,
79
92
  };
80
- this.mainSession = `sess-${opts.id}-main`;
93
+ // the session id IS the directory name — one directory per incarnation
94
+ this.mainSession = path.basename(opts.sessionDir);
81
95
  this.currentSession = this.mainSession;
82
96
  }
83
97
  skillRoots;
@@ -91,17 +105,6 @@ export class Agent {
91
105
  /* keep previous cache */
92
106
  }
93
107
  }
94
- skillsListing() {
95
- if (this.skillsCache.length === 0) {
96
- return ("## Skills\n" +
97
- "No skills exist yet. When you develop a reusable procedure worth keeping " +
98
- "(build steps, checklists, project conventions), distill it into a durable playbook " +
99
- "with save_skill so future sessions can load it via load_skill.");
100
- }
101
- return ("## Skills (reusable playbooks)\n" +
102
- "When the current task matches a description below, call load_skill(name) first and follow it.\n" +
103
- this.skillsCache.map((s) => `- ${s.name}: ${s.description || "(no description)"}`).join("\n"));
104
- }
105
108
  callLlm(messages, tools, onDelta) {
106
109
  const fn = this.opts.chatFn ?? chatStream;
107
110
  return fn(this.opts.llm, messages, tools, this.abort?.signal, onDelta);
@@ -124,18 +127,49 @@ export class Agent {
124
127
  async init() {
125
128
  await this.log.load();
126
129
  await fs.mkdir(this.workspace, { recursive: true });
127
- // seed persistent context files if missing
128
- await this.seed("AGENTS.md", "# Project knowledge\n\n(Describe conventions, build commands, and gotchas here.)\n");
129
- await this.seed("MEMORY.md", "# Memory\n");
130
- const goalText = await this.readGoalFile();
131
- if (goalText !== null)
132
- this.goal = this.parseGoalFile(goalText);
133
- else
134
- await this.writeGoalFile();
130
+ // goal lives next to the session log (dataDir), NOT in the workspace —
131
+ // migrate a legacy workspace GOAL.md once, then never touch the workspace
132
+ await this.migrateGoalFromWorkspace();
133
+ const stored = await this.readGoalStore();
134
+ this.goal = stored ?? { text: "", status: "active", updatedAt: new Date().toISOString() };
135
135
  if (this.opts.restoreSession)
136
136
  await this.restoreFromLog();
137
137
  await this.refreshSkills();
138
138
  }
139
+ /** harness-managed files inside the session directory */
140
+ get goalFile() {
141
+ return path.join(this.opts.sessionDir, "goal.md");
142
+ }
143
+ get memoryFile() {
144
+ return path.join(this.opts.sessionDir, "memory.md");
145
+ }
146
+ async readGoalStoreRaw() {
147
+ return fs.readFile(this.goalFile, "utf8").catch(() => null);
148
+ }
149
+ async readGoalStore() {
150
+ const raw = await this.readGoalStoreRaw();
151
+ return raw === null ? null : this.parseGoalFile(raw);
152
+ }
153
+ /** One-time import of a pre-0.6.0 workspace GOAL.md; content is preserved. */
154
+ async migrateGoalFromWorkspace() {
155
+ const legacy = path.join(this.workspace, "GOAL.md");
156
+ let wsText;
157
+ try {
158
+ wsText = await fs.readFile(legacy, "utf8");
159
+ }
160
+ catch {
161
+ return; // nothing to migrate
162
+ }
163
+ const existing = await this.readGoalStoreRaw();
164
+ if (existing === null)
165
+ await fs.writeFile(this.goalFile, wsText, "utf8");
166
+ await fs.rm(legacy).catch(() => { });
167
+ await this.log.append("system_note", this.currentSession, this.currentBranch, {
168
+ event: "goal-migrated",
169
+ from: "GOAL.md",
170
+ to: this.goalFile,
171
+ });
172
+ }
139
173
  /**
140
174
  * Rebuild the in-memory conversation from the JSONL event log so a restart
141
175
  * continues where the agent left off instead of starting blank.
@@ -237,24 +271,20 @@ export class Agent {
237
271
  });
238
272
  }
239
273
  }
240
- async seed(file, content) {
241
- const p = path.join(this.workspace, file);
242
- try {
243
- await fs.access(p);
244
- }
245
- catch {
246
- await fs.writeFile(p, content, "utf8");
247
- }
248
- }
249
- readGoalFile() {
250
- return fs.readFile(path.join(this.workspace, "GOAL.md"), "utf8").catch(() => null);
251
- }
252
274
  parseGoalFile(text) {
253
275
  // humans and agents may append their own status lines — latest wins
254
276
  const all = [...text.matchAll(/status:\s*(\w+)/gi)];
255
277
  const last = all[all.length - 1]?.[1];
256
278
  const status = last === "done" ? "done" : last === "paused" ? "paused" : "active";
257
- return { text: text.trim(), status, updatedAt: new Date().toISOString() };
279
+ // keep bookkeeping lines out of the injected goal text
280
+ let body = text.trim();
281
+ for (let i = 0; i < 4; i++) {
282
+ const stripped = body.replace(/\n+(?:status|updated):[^\n]*$/i, "").trimEnd();
283
+ if (stripped === body)
284
+ break;
285
+ body = stripped;
286
+ }
287
+ return { text: body, status, updatedAt: new Date().toISOString() };
258
288
  }
259
289
  async writeGoalFile() {
260
290
  // don't let previously-appended bookkeeping lines accumulate in the body
@@ -265,7 +295,7 @@ export class Agent {
265
295
  break;
266
296
  body = stripped;
267
297
  }
268
- await fs.writeFile(path.join(this.workspace, "GOAL.md"), `${body}\n\nstatus: ${this.goal.status}\nupdated: ${this.goal.updatedAt}\n`, "utf8");
298
+ await fs.writeFile(this.goalFile, `${body}\n\nstatus: ${this.goal.status}\nupdated: ${this.goal.updatedAt}\n`, "utf8");
269
299
  }
270
300
  async setGoal(text) {
271
301
  this.goal = { text, status: "active", updatedAt: new Date().toISOString() };
@@ -290,6 +320,7 @@ export class Agent {
290
320
  stats: { ...this.stats },
291
321
  model: this.opts.llm.model,
292
322
  provider: this.opts.provider,
323
+ sessionDir: this.opts.sessionDir,
293
324
  };
294
325
  }
295
326
  /** Queue a user prompt; wakes the loop if needed. Returns immediately. */
@@ -354,7 +385,7 @@ export class Agent {
354
385
  await this.sleepInterruptible(this.opts.continueDelayMs);
355
386
  if (this.stopRequested)
356
387
  break;
357
- const nudge = "Continue working toward the goal in GOAL.md. If you are blocked, explain why briefly.";
388
+ const nudge = "Continue working toward the current goal. If you are blocked, explain why briefly.";
358
389
  await this.log.append("prompt", this.currentSession, this.currentBranch, {
359
390
  source: "harness",
360
391
  text: nudge,
@@ -491,6 +522,54 @@ export class Agent {
491
522
  });
492
523
  continue;
493
524
  }
525
+ if (call.function.name === "set_goal") {
526
+ const a = safeParse(call.function.arguments);
527
+ const text = String(a.text ?? "").trim();
528
+ if (text)
529
+ await this.setGoal(text);
530
+ this.messages.push({
531
+ role: "tool",
532
+ tool_call_id: call.id,
533
+ content: text ? "goal updated" : "empty goal rejected",
534
+ });
535
+ continue;
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
+ }
562
+ if (call.function.name === "set_memory") {
563
+ const a = safeParse(call.function.arguments);
564
+ const content = String(a.content ?? "").slice(0, 32_000);
565
+ await fs.writeFile(this.memoryFile, content, "utf8");
566
+ this.messages.push({
567
+ role: "tool",
568
+ tool_call_id: call.id,
569
+ content: "memory saved (injected into future prompts)",
570
+ });
571
+ continue;
572
+ }
494
573
  await this.log.append("tool_call", this.currentSession, this.currentBranch, {
495
574
  callId: call.id,
496
575
  name: call.function.name,
@@ -518,13 +597,9 @@ export class Agent {
518
597
  throw new Error("runaway detection: too many turns in one round (>200)");
519
598
  }
520
599
  buildMessages() {
521
- const sys = [SYSTEM_TEMPLATE];
522
- if (this.goal.text) {
523
- sys.push(`## Current goal (${this.goal.status})\n${this.goal.text}`);
524
- }
525
- sys.push(this.skillsListing());
600
+ // deliberately static: [system] + append-only history keeps prefix caches hot
526
601
  const hasSystem = this.messages[0]?.role === "system";
527
- const head = [{ role: "system", content: sys.join("\n\n") }];
602
+ const head = [{ role: "system", content: SYSTEM_TEMPLATE }];
528
603
  return hasSystem ? [...head, ...this.messages.slice(1)] : [...head, ...this.messages];
529
604
  }
530
605
  async handleFinish(argsJson) {
@@ -627,7 +702,7 @@ export class Agent {
627
702
  {
628
703
  role: "user",
629
704
  content: `[harness] Context was compacted: ${oldCount} earlier messages were summarized. ` +
630
- "Persistent files (GOAL.md / AGENTS.md / MEMORY.md) are still on disk re-read them when needed.\n\n" +
705
+ "Goal and notes are managed by the harness (AGENTS.md / memory.md are injected into your prompt when present).\n\n" +
631
706
  `## Summary of earlier conversation\n${summary}`,
632
707
  },
633
708
  ...this.messages.slice(cut),
@@ -723,7 +798,7 @@ export class Agent {
723
798
  this.currentBranch = branch;
724
799
  }
725
800
  }
726
- /** workspace tools + agent-meta tools (finish / report_progress) */
801
+ /** workspace tools + agent-meta tools (finish / report_progress / set_goal / set_memory) */
727
802
  function allToolSpecs() {
728
803
  return [
729
804
  ...toolSpecs(),
@@ -731,7 +806,7 @@ function allToolSpecs() {
731
806
  type: "function",
732
807
  function: {
733
808
  name: "finish",
734
- description: "End the current round. Call with goalComplete=true only when the goal in GOAL.md is fully achieved.",
809
+ description: "End the current round. Call with goalComplete=true only when the current goal (shown in your prompt) is fully achieved.",
735
810
  parameters: {
736
811
  type: "object",
737
812
  properties: {
@@ -759,6 +834,54 @@ function allToolSpecs() {
759
834
  },
760
835
  },
761
836
  },
837
+ {
838
+ type: "function",
839
+ function: {
840
+ name: "set_goal",
841
+ description: "Replace the harness-managed goal text. Use when the objective itself changes — not for routine updates.",
842
+ parameters: {
843
+ type: "object",
844
+ properties: { text: { type: "string" } },
845
+ required: ["text"],
846
+ },
847
+ },
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
+ },
865
+ {
866
+ type: "function",
867
+ function: {
868
+ name: "set_memory",
869
+ description: "Overwrite your durable notes (memory.md). Keep them terse: decisions, gotchas, where you left off.",
870
+ parameters: {
871
+ type: "object",
872
+ properties: { content: { type: "string" } },
873
+ required: ["content"],
874
+ },
875
+ },
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
+ },
762
885
  ];
763
886
  }
764
887
  function str(v) {
package/dist/master.js CHANGED
@@ -3,7 +3,8 @@
3
3
  * Agents are in-process async loops (I/O bound only); all CPU-heavy work is
4
4
  * delegated to subprocesses managed by the bash tool with hard timeouts.
5
5
  */
6
- import { readFileSync, existsSync, mkdirSync, writeFileSync } from "node:fs";
6
+ import { readFileSync, existsSync, mkdirSync, writeFileSync, readdirSync, statSync, renameSync } from "node:fs";
7
+ import { randomUUID } from "node:crypto";
7
8
  import path from "node:path";
8
9
  import os from "node:os";
9
10
  import { Agent } from "./agent/agent.js";
@@ -192,8 +193,14 @@ export class Master {
192
193
  // single low-frequency tick for everything periodic (idle cost ≈ 0)
193
194
  setInterval(() => void this.tick(), 15_000).unref();
194
195
  }
195
- /** Create an agent; optionally persist it to the config file. */
196
- async addAgent(ac, persist = false) {
196
+ /**
197
+ * Create an agent; optionally persist it to the config file.
198
+ * Each incarnation gets its own session directory under
199
+ * <dataDir>/sessions/<agentId>-<uuid>/ (chat.jsonl, goal.md, memory.md).
200
+ * Restarts reuse the latest existing session; fresh creations never touch
201
+ * an older one's history.
202
+ */
203
+ async addAgent(ac, opts = {}) {
197
204
  if (this.agents.has(ac.id))
198
205
  throw new Error(`agent id already exists: ${ac.id}`);
199
206
  // provider resolution: inline overrides > named provider > legacy llm block
@@ -210,12 +217,13 @@ export class Master {
210
217
  };
211
218
  if (!llm.model)
212
219
  throw new Error(`agent ${ac.id}: no model configured (set model on the agent or on its provider)`);
213
- const logFile = path.join(this.config.dataDir, `${ac.id}.jsonl`);
220
+ const sessionDir = this.resolveSessionDir(ac.id, opts.fresh === true);
221
+ await mkdirSync(sessionDir, { recursive: true });
214
222
  const agent = new Agent({
215
223
  id: ac.id,
216
224
  workspace: path.resolve(ac.workspace),
217
225
  llm,
218
- logFile,
226
+ sessionDir,
219
227
  progressIntervalMs: this.config.progressIntervalMs,
220
228
  autoContinue: true,
221
229
  ...(this.config.contextTokenBudget ? { contextTokenBudget: this.config.contextTokenBudget } : {}),
@@ -225,12 +233,68 @@ export class Master {
225
233
  agent.log.onEvent = (e) => printAgentEvent(e);
226
234
  await agent.init();
227
235
  this.agents.set(ac.id, agent);
228
- if (persist) {
236
+ if (opts.persist) {
229
237
  this.config.agents.push(ac);
230
238
  this.saveConfig();
231
239
  }
232
240
  return agent;
233
241
  }
242
+ /** sessions root + helpers */
243
+ sessionsRoot() {
244
+ return path.join(this.config.dataDir, "sessions");
245
+ }
246
+ findLatestSessionDir(agentId) {
247
+ let dirs = [];
248
+ try {
249
+ dirs = readdirSync(this.sessionsRoot())
250
+ .filter((d) => d === agentId || d.startsWith(`${agentId}-`))
251
+ .sort((a, b) => {
252
+ // newest chat.jsonl wins
253
+ const ma = this.sessionMtime(path.join(this.sessionsRoot(), a));
254
+ const mb = this.sessionMtime(path.join(this.sessionsRoot(), b));
255
+ return mb - ma;
256
+ });
257
+ }
258
+ catch {
259
+ return null;
260
+ }
261
+ return dirs[0] ? path.join(this.sessionsRoot(), dirs[0]) : null;
262
+ }
263
+ sessionMtime(dir) {
264
+ try {
265
+ return statSync(path.join(dir, "chat.jsonl")).mtimeMs;
266
+ }
267
+ catch {
268
+ return 0;
269
+ }
270
+ }
271
+ resolveSessionDir(agentId, fresh) {
272
+ const root = this.sessionsRoot();
273
+ mkdirSync(root, { recursive: true });
274
+ // one-time migration from the ≤0.5.0 flat layout (<dataDir>/<id>.jsonl)
275
+ if (!fresh) {
276
+ const legacy = path.join(this.config.dataDir, `${agentId}.jsonl`);
277
+ if (existsSync(legacy)) {
278
+ const target = path.join(root, agentId);
279
+ if (!existsSync(target)) {
280
+ mkdirSync(target, { recursive: true });
281
+ renameSync(legacy, path.join(target, "chat.jsonl"));
282
+ console.log(`[teapot] migrated session storage: ${agentId}.jsonl → sessions/${agentId}/chat.jsonl`);
283
+ }
284
+ }
285
+ }
286
+ if (!fresh) {
287
+ const existing = this.findLatestSessionDir(agentId);
288
+ if (existing)
289
+ return existing; // restart → continue where we left off
290
+ }
291
+ // fresh incarnation: guaranteed-unique directory
292
+ let sid = "";
293
+ do {
294
+ sid = `${agentId}-${randomUUID().slice(0, 8)}`;
295
+ } while (existsSync(path.join(root, sid)));
296
+ return path.join(root, sid);
297
+ }
234
298
  async removeAgent(id) {
235
299
  const agent = this.agents.get(id);
236
300
  if (!agent)
@@ -143,7 +143,7 @@ export function buildApp(master) {
143
143
  }
144
144
  const id = (body.id?.trim() || path.basename(ws)).replace(/[^\w.-]/g, "-").slice(0, 40);
145
145
  try {
146
- const agent = await master.addAgent({ id, workspace: ws, provider: body.provider, model: body.model }, true);
146
+ const agent = await master.addAgent({ id, workspace: ws, provider: body.provider, model: body.model }, { persist: true, fresh: true });
147
147
  if (body.start !== false)
148
148
  agent.start("created via web");
149
149
  return c.json({ ok: true, agent: agent.snapshot() });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "teapot-coding-agent",
3
- "version": "0.5.0",
3
+ "version": "0.7.0",
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",