Skip to content

Commit 75c66ac

Browse files
committed
feat: update subagent announce + archive
1 parent 1673a22 commit 75c66ac

13 files changed

Lines changed: 628 additions & 197 deletions

docs/concepts/session-tool.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -127,15 +127,17 @@ Parameters:
127127
- `task` (required)
128128
- `label?` (optional; used for logs/UI)
129129
- `model?` (optional; overrides the sub-agent model; invalid values error)
130-
- `timeoutSeconds?` (default 0; 0 = fire-and-forget)
131-
- `cleanup?` (`delete|keep`, default `delete`)
130+
- `timeoutSeconds?` (optional; omit for long-running jobs; if set, Clawdbot aborts the sub-agent when the timeout elapses)
131+
- `cleanup?` (`delete|keep`, default `keep`)
132132

133133
Behavior:
134-
- Starts a new `subagent:<uuid>` session with `deliver: false`.
134+
- Starts a new `agent:<id>:subagent:<uuid>` session with `deliver: false`.
135135
- Sub-agents default to the full tool set **minus session tools** (configurable via `agent.subagents.tools`).
136136
- Sub-agents are not allowed to call `sessions_spawn` (no sub-agent → sub-agent spawning).
137137
- After completion (or best-effort wait), Clawdbot runs a sub-agent **announce step** and posts the result to the requester chat provider.
138138
- Reply exactly `ANNOUNCE_SKIP` during the announce step to stay silent.
139+
- Sub-agent sessions are auto-archived after `agent.subagents.archiveAfterMinutes` (default: 60).
140+
- Announce replies include a stats line (runtime, tokens, sessionKey/sessionId, transcript path, and optional cost).
139141

140142
## Sandbox Session Visibility
141143

docs/gateway/configuration.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -757,6 +757,10 @@ If you configure the same alias name (case-insensitive) yourself, your value win
757757
target: "last"
758758
},
759759
maxConcurrent: 3,
760+
subagents: {
761+
maxConcurrent: 1,
762+
archiveAfterMinutes: 60
763+
},
760764
bash: {
761765
backgroundMs: 10000,
762766
timeoutSec: 1800,
@@ -805,6 +809,11 @@ of `every`, keep `HEARTBEAT.md` tiny, and/or choose a cheaper `model`.
805809
- `timeoutSec`: auto-kill after this runtime (seconds, default 1800)
806810
- `cleanupMs`: how long to keep finished sessions in memory (ms, default 1800000)
807811

812+
`agent.subagents` configures sub-agent defaults:
813+
- `maxConcurrent`: max concurrent sub-agent runs (default 1)
814+
- `archiveAfterMinutes`: auto-archive sub-agent sessions after N minutes (default 60; set `0` to disable)
815+
- `tools.allow` / `tools.deny`: per-subagent tool allow/deny policy (deny wins)
816+
808817
`agent.tools` configures a global tool allow/deny policy (deny wins).
809818
This is applied even when the Docker sandbox is **off**.
810819

docs/tools/index.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,18 +150,20 @@ Core actions:
150150
Notes:
151151
- Use `delayMs` (defaults to 2000) to avoid interrupting an in-flight reply.
152152

153-
### `sessions_list` / `sessions_history` / `sessions_send`
153+
### `sessions_list` / `sessions_history` / `sessions_send` / `sessions_spawn`
154154
List sessions, inspect transcript history, or send to another session.
155155

156156
Core parameters:
157157
- `sessions_list`: `kinds?`, `limit?`, `activeMinutes?`, `messageLimit?` (0 = none)
158158
- `sessions_history`: `sessionKey`, `limit?`, `includeTools?`
159159
- `sessions_send`: `sessionKey`, `message`, `timeoutSeconds?` (0 = fire-and-forget)
160+
- `sessions_spawn`: `task`, `label?`, `model?`, `timeoutSeconds?`, `cleanup?`
160161

161162
Notes:
162163
- `main` is the canonical direct-chat key; global/unknown are hidden.
163164
- `messageLimit > 0` fetches last N messages per session (tool messages filtered).
164165
- `sessions_send` waits for final completion when `timeoutSeconds > 0`.
166+
- `sessions_spawn` starts a sub-agent run and posts an announce reply back to the requester chat.
165167
- `sessions_send` runs a reply‑back ping‑pong (reply `REPLY_SKIP` to stop; max turns via `session.agentToAgent.maxPingPongTurns`, 0–5).
166168
- After the ping‑pong, the target agent runs an **announce step**; reply `ANNOUNCE_SKIP` to suppress the announcement.
167169

docs/tools/subagents.md

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ read_when:
77

88
# Sub-agents
99

10-
Sub-agents are background agent runs spawned from an existing agent run. They run in their own session (`subagent:<uuid>`) and, when finished, **announce** their result back to the requester chat provider.
10+
Sub-agents are background agent runs spawned from an existing agent run. They run in their own session (`agent:<id>:subagent:<uuid>`) and, when finished, **announce** their result back to the requester chat provider.
1111

1212
Primary goals:
1313
- Parallelize “research / long task / slow tool” work without blocking the main run.
@@ -25,8 +25,15 @@ Tool params:
2525
- `task` (required)
2626
- `label?` (optional)
2727
- `model?` (optional; overrides the sub-agent model; invalid values are skipped and the sub-agent runs on the default model with a warning in the tool result)
28-
- `timeoutSeconds?` (default `0`; `0` = fire-and-forget)
29-
- `cleanup?` (`delete|keep`, default `delete`)
28+
- `timeoutSeconds?` (optional; omit for long-running jobs; when set, Clawdbot waits up to N seconds and aborts the sub-agent if it is still running)
29+
- `cleanup?` (`delete|keep`, default `keep`)
30+
31+
Auto-archive:
32+
- Sub-agent sessions are automatically archived after `agent.subagents.archiveAfterMinutes` (default: 60).
33+
- Archive uses `sessions.delete` and renames the transcript to `*.deleted.<timestamp>` (same folder).
34+
- `cleanup: "delete"` archives immediately after announce (still keeps the transcript via rename).
35+
- Auto-archive is best-effort; pending timers are lost if the gateway restarts.
36+
- Timeouts do **not** auto-archive; they only stop the run. The session remains until auto-archive.
3037

3138
## Announce
3239

@@ -35,6 +42,12 @@ Sub-agents report back via an announce step:
3542
- If the sub-agent replies exactly `ANNOUNCE_SKIP`, nothing is posted.
3643
- Otherwise the announce reply is posted to the requester chat provider via the gateway `send` method.
3744

45+
Announce payloads include a stats line at the end:
46+
- Runtime (e.g., `runtime 5m12s`)
47+
- Token usage (input/output/total)
48+
- Estimated cost when model pricing is configured (`models.providers.*.models[].cost`)
49+
- `sessionKey`, `sessionId`, and transcript path (so the main agent can fetch history via `sessions_history` or inspect the file on disk)
50+
3851
## Tool Policy (sub-agent tools)
3952

4053
By default, sub-agents get **all tools except session tools**:

src/agents/clawdbot-tools.subagents.test.ts

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ describe("subagents", () => {
9090
const result = await tool.execute("call1", {
9191
task: "do thing",
9292
timeoutSeconds: 1,
93+
cleanup: "delete",
9394
});
9495
expect(result.details).toMatchObject({ status: "ok", reply: "result" });
9596

@@ -105,11 +106,10 @@ describe("subagents", () => {
105106
expect(first?.deliver).toBe(false);
106107
expect(first?.sessionKey?.startsWith("agent:main:subagent:")).toBe(true);
107108

108-
expect(sendParams).toMatchObject({
109-
provider: "discord",
110-
to: "channel:req",
111-
message: "announce now",
112-
});
109+
expect(sendParams.provider).toBe("discord");
110+
expect(sendParams.to).toBe("channel:req");
111+
expect(sendParams.message ?? "").toContain("announce now");
112+
expect(sendParams.message ?? "").toContain("Stats:");
113113
expect(deletedKey?.startsWith("agent:main:subagent:")).toBe(true);
114114
});
115115

@@ -195,11 +195,10 @@ describe("subagents", () => {
195195
await new Promise((resolve) => setTimeout(resolve, 0));
196196
await new Promise((resolve) => setTimeout(resolve, 0));
197197

198-
expect(sendParams).toMatchObject({
199-
provider: "whatsapp",
200-
to: "+123",
201-
message: "hello from sub",
202-
});
198+
expect(sendParams.provider).toBe("whatsapp");
199+
expect(sendParams.to).toBe("+123");
200+
expect(sendParams.message ?? "").toContain("hello from sub");
201+
expect(sendParams.message ?? "").toContain("Stats:");
203202
});
204203

205204
it("sessions_spawn applies a model to the child session", async () => {

src/agents/pi-embedded-runner.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,23 @@ export type EmbeddedPiRunMeta = {
9696
aborted?: boolean;
9797
};
9898

99+
function buildModelAliasLines(cfg?: ClawdbotConfig) {
100+
const models = cfg?.agent?.models ?? {};
101+
const entries: Array<{ alias: string; model: string }> = [];
102+
for (const [keyRaw, entryRaw] of Object.entries(models)) {
103+
const model = String(keyRaw ?? "").trim();
104+
if (!model) continue;
105+
const alias = String(
106+
(entryRaw as { alias?: string } | undefined)?.alias ?? "",
107+
).trim();
108+
if (!alias) continue;
109+
entries.push({ alias, model });
110+
}
111+
return entries
112+
.sort((a, b) => a.alias.localeCompare(b.alias))
113+
.map((entry) => `- ${entry.alias}: ${entry.model}`);
114+
}
115+
99116
type ApiKeyInfo = {
100117
apiKey: string;
101118
profileId?: string;
@@ -495,6 +512,7 @@ export async function compactEmbeddedPiSession(params: {
495512
runtimeInfo,
496513
sandboxInfo,
497514
toolNames: tools.map((tool) => tool.name),
515+
modelAliasLines: buildModelAliasLines(params.config),
498516
userTimezone,
499517
userTime,
500518
}),
@@ -795,6 +813,7 @@ export async function runEmbeddedPiAgent(params: {
795813
runtimeInfo,
796814
sandboxInfo,
797815
toolNames: tools.map((tool) => tool.name),
816+
modelAliasLines: buildModelAliasLines(params.config),
798817
userTimezone,
799818
userTime,
800819
}),

0 commit comments

Comments
 (0)