Durable attached subagents
Run the parent and its child on a durable host:
import { SubagentRuntime } from "@effect-agent/capabilities/Subagent";
import { SubagentReservationsMemoryLive } from "@effect-agent/capabilities/SubagentReservations";
import { IdGenerator } from "@effect-agent/core/IdGenerator";
import { NodeDurableHost } from "@effect-agent/platform-node";
import { Layer } from "effect";
import { Coordinator } from "./coordinator.ts";
import { Research, ResearchFailed } from "./delegation.ts";
import { definitions, ModelLive, OpenAiLive } from "./node-agent.ts";
import { TravelToolsLive } from "./tools.ts";
const ResearchLive = SubagentRuntime.layer(Research, ModelLive, {
mapChildFailure: (error) => ResearchFailed.make({ reason: error._tag }),
}).pipe(Layer.provide(TravelToolsLive));
export const HostLive = NodeDurableHost.layer(
[
{ agent: Coordinator, model: ModelLive, definitions },
{ agent: Research.target, model: ModelLive, definitions },
],
{
filename: "./agents.sqlite",
deploymentId: "attached-research",
producerId: "worker-start-001",
workerConcurrency: 4,
},
).pipe(
Layer.provide(ResearchLive),
Layer.provide(SubagentReservationsMemoryLive),
Layer.provide(IdGenerator.layer),
Layer.provide(TravelToolsLive),
Layer.provide(OpenAiLive),
);Each { agent, model, definitions } entry registers an agent with the host. Register both the coordinator and the exact child used by Research, so recovery can find them after a restart. definitions contains the code versions; SQLite stores accepted work and recorded results.
Save this as durable-delegation-host.ts. It reuses the attached example files and the provider setup from node-agent.ts.
Define the delegation
import * as Subagent from "@effect-agent/capabilities/Subagent";
import { SubagentPolicy } from "@effect-agent/capabilities/Subagent";
import { Effect, Schema } from "effect";
import { Researcher } from "./researcher.ts";
export class ResearchFailed extends Schema.TaggedError<ResearchFailed>()("ResearchFailed", {
reason: Schema.String,
}) {}
export const Research = Subagent.make("delegate_research_activities", {
description: "Delegate activity research for one city and focus. Returns a shortlist.",
target: Researcher,
success: Schema.Struct({
activities: Schema.Array(Schema.String),
partial: Schema.Boolean,
}),
failure: ResearchFailed,
failureMode: "error",
projectResult: (output, { budgetExhausted }) =>
Effect.succeed({
activities: output.activities,
partial: budgetExhausted,
// researchNotes stays in the child's thread.
}),
policy: SubagentPolicy.make({
maxChildren: 2,
maxConcurrency: 2,
maxTurns: 4,
maxToolCalls: 4,
maxDuration: "30 seconds",
maxResultBytes: 4_096,
}),
});The declaration is the same for ephemeral and durable attached execution. Put Research.tool in the parent's toolkit. The host supplies durability; no different subagent constructor is needed.
Start the host
import { NodeDurableHost } from "@effect-agent/platform-node";
import { NodeRuntime } from "@effect/platform-node";
import { Effect } from "effect";
import { HostLive } from "./durable-delegation-host.ts";
NodeRuntime.runMain(NodeDurableHost.run.pipe(Effect.provide(HostLive)));Use the Node.js setup to submit Coordinator with { city: "Lisbon" }. The Cloudflare host supports the same attached lifecycle.
Waiting and recovery
The parent's next model call waits until its current tool batch settles. Children can run concurrently; the waiting parent releases its execution slot for other work.
After a restart, recovery reconnects to the same child and restores recorded results, policy, and budget reservations. Uncertain admission does not launch a replacement child. Keep the registered code versions needed by pending work available.
Aborting the parent propagates cancellation to its children and joins their terminal outcomes. Cancellation cannot undo external effects. See recovery details and failure handling.
To keep the parent responding while its child runs, use durable background subagents.