Official
subagents
Agents start subagents on any of your enabled agents and models, and you can @-tag an agent, model and effort in the composer to hand part of a message to one.
The app opens the listing; nothing installs until an agent in your Plugins workspace has read the files and you enable the plugin. In a terminal: cvg install convergence/subagents@0.1.0
Permissions in 0.1.0
Files
spawn.ts3 KB
// Run a tool call in its displayed subagent, or in the plugin owning a type.
// Model failures remain tool results, with the displayed run ended first.
import * as Data from "effect/Data";
import * as Effect from "effect/Effect";
import * as z from "zod";
import { parse, promise } from "convergence/effect";
import type { Api, ToolContext } from "convergence";
import { TOOL, titleOf } from "./lib.ts";
import type { SpawnInput } from "./lib.ts";
class ModelFailed extends Data.TaggedError("ModelFailed")<{ readonly cause: unknown }> {}
const errorFields = z.object({ name: z.string().optional(), message: z.string().optional() });
const failed = (text: string) => ({ content: [{ type: "text", text }], isError: true });
export const spawn = Effect.fn("Subagents.spawn")(function* (
api: { models: Pick<Api["models"], "prompt">; subagents: Pick<Api["subagents"], "run"> },
input: SpawnInput,
ctx: ToolContext,
) {
const prompt = input.prompt.trim();
if (!prompt) return failed(`${TOOL} needs a prompt.`);
if (input.type) {
const type = yield* parse("subagent type", z.string(), input.type);
const result = yield* promise("subagents.run", () => api.subagents.run({ type, input: { ...input, prompt } }, ctx));
return result ?? "The subagent finished without a result.";
}
const agentId = input.agent || ctx.agentId;
if (!agentId) return failed("Name the agent to run the subagent on.");
const subagent = yield* promise("subagent.start", () =>
ctx.subagent.start({
title: input.title || titleOf(prompt),
agent: agentId,
model: input.model,
effort: input.effort,
prompt,
}),
);
return yield* Effect.tryPromise({
try: () =>
api.models.prompt({
agentId,
model: input.model,
effort: input.effort,
prompt,
workspaceId: ctx.workspaceId,
subagent,
signal: ctx.signal,
}),
catch: (cause) => new ModelFailed({ cause }),
}).pipe(
Effect.map((answer) => {
if (answer.status === "completed") {
subagent.end({ status: "completed", summary: answer.text });
return answer.text || "The subagent finished without a reply.";
}
const why =
answer.status === "cancelled"
? "The subagent was stopped."
: `The subagent failed: ${answer.message ?? "no reason given"}`;
subagent.end({ status: answer.status === "cancelled" ? "cancelled" : "failed", summary: why });
return failed(why);
}),
Effect.catchTag("ModelFailed", ({ cause }) =>
Effect.sync(() => {
const fields = errorFields.safeParse(cause);
const stopped = fields.success && fields.data.name === "AbortError";
const message = fields.success ? (fields.data.message ?? String(cause)) : String(cause);
subagent.end({ status: stopped ? "cancelled" : "failed", summary: stopped ? "Stopped." : message });
return failed(stopped ? "The subagent was stopped." : `The subagent could not run: ${message}`);
}),
),
);
});Versions
| Version | Published | Plugin API | Size | Permissions | Status |
|---|---|---|---|---|---|
| 0.1.0latest | Oct 5, 2026 | >=2 <3 | 8.6 KB | 2 permissions | Listed |
No comments yet.