Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,25 @@
# Changelog

## 0.5.65 — 2026-09-10

Deterministic capability routing for delegated agents and fail-closed package
tool ownership.

- Add `required_tool` to `spawn_agent`. PilotSwarm resolves the unique
caller-visible creatable agent that declares the tool, binds its complete
definition, and treats `agent_name` plus `required_tool` as an ownership
assertion rather than detached tool selection.
- Pin the exact shared or private package copy through child creation and
worker rehydration. Ad-hoc children no longer inherit package identity,
privileged roles, or package handlers; detached package tools are dropped
when inherited and rejected when explicitly requested.
- Reject package tools that collide with Copilot-native, PilotSwarm control,
or deployment tool names. Report deterministic package-binding failures as
non-retryable turn errors.
- Freeze orchestration 1.0.73 and introduce 1.0.74 for the new caller-aware
capability-resolution activity. Retry transient Windows directory rename
failures during snapshot hydration with a bounded backoff.

## 0.5.64 — 2026-09-09

Cluster and user feature flighting, bounded native Copilot delegation, and
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

> **Experimental** — This project is under active development and not yet ready for production use. APIs may change without notice.

> **Latest release: v0.5.64** — Feature flighting, native Copilot tasks, and clearer live task activity.
> **Latest release: v0.5.65** — Capability-owned delegation and fail-closed package tool routing.

A durable execution runtime for [GitHub Copilot SDK](https://github.com/github/copilot-sdk) agents. Crash recovery, durable timers, session dehydration, and multi-node scaling — powered by [duroxide](https://github.com/microsoft/duroxide). Just add a connection string.

Expand Down
6 changes: 6 additions & 0 deletions docs/developer/building/sdk-agents.md
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,12 @@ The worker supplies the actual agent definitions and tool handlers. The client o

For known named agents, use `spawn_agent(agent_name="...")`.

When the caller needs a tool but should not hard-code an agent name, use
`spawn_agent(required_tool="tool_name")`. PilotSwarm resolves the unique
caller-visible creatable owner and binds its complete definition. Combining
`agent_name` and `required_tool` verifies that the named agent declares the
tool. Do not pass package-owned tools to ad hoc children with `tool_names`.

Use `task=` only for truly ad hoc custom sub-agents. Do not use `task="sweeper"` or `task="resourcemgr"` for named system agents.

### Sub-agent models
Expand Down
4 changes: 4 additions & 0 deletions docs/developer/reference/agent-contracts.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,16 @@ Why it matters:
Contract:

- if an agent is already known by name, spawn it with `spawn_agent(agent_name="...")`
- if delegation requires a capability but should not hard-code an agent name, use `spawn_agent(required_tool="...")`; PilotSwarm binds the unique caller-visible creatable agent that declares it
- combining `agent_name` and `required_tool` asserts that the named agent owns the tool; it does not attach the tool to a different agent
- use `task=` only for ad hoc custom agents
- do not pass package-owned tools through `tool_names`; package prompt, skills, startup contract, and handlers stay attached to their owning named-agent definition
- known system agents like `sweeper` and `resourcemgr` should not be created via `task="..."`

Why it matters:

- named agents carry canonical metadata
- capability routing preserves shared/private package visibility and exact package-copy identity across workers
- system-agent titles and IDs depend on that named-agent path
- generic `task=` spawns can lose `agentId`, `title`, and expected behavior

Expand Down
16 changes: 8 additions & 8 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions packages/app/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "pilotswarm",
"version": "0.5.64",
"version": "0.5.65",
"description": "PilotSwarm application package: terminal UI, browser portal + Web API server, and MCP server — one install, three bins.",
"type": "module",
"license": "MIT",
Expand Down Expand Up @@ -81,7 +81,7 @@
"ink": "^6.8.0",
"jose": "^6.2.2",
"mermaid": "^11.16.0",
"pilotswarm-sdk": "0.5.64",
"pilotswarm-sdk": "0.5.65",
"react": "^19.2.4",
"react-dom": "^19.2.4",
"ws": "^8.18.2"
Expand Down
6 changes: 3 additions & 3 deletions packages/horizon-store/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "pilotswarm-horizon-store",
"version": "0.5.64",
"version": "0.5.65",
"type": "module",
"description": "HorizonDB-backed enhanced facts and graph providers for PilotSwarm.",
"main": "./dist/src/index.js",
Expand Down Expand Up @@ -34,14 +34,14 @@
"pg": "^8.13.1"
},
"peerDependencies": {
"pilotswarm-sdk": "0.5.64"
"pilotswarm-sdk": "0.5.65"
},
"devDependencies": {
"@github/copilot": "1.0.83",
"@github/copilot-sdk": "1.0.13",
"@types/node": "^24.0.0",
"@types/pg": "^8.11.10",
"pilotswarm-sdk": "0.5.64",
"pilotswarm-sdk": "0.5.65",
"typescript": "^5.6.0"
},
"engines": {
Expand Down
6 changes: 3 additions & 3 deletions packages/sdk/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "pilotswarm-sdk",
"version": "0.5.64",
"version": "0.5.65",
"description": "A durable execution runtime for GitHub Copilot SDK agents. Crash recovery, durable timers, session dehydration, and multi-node scaling — powered by duroxide.",
"type": "module",
"main": "./dist/index.js",
Expand Down Expand Up @@ -88,7 +88,7 @@
"pg": "^8.18.0"
},
"peerDependencies": {
"pilotswarm-horizon-store": "0.5.64"
"pilotswarm-horizon-store": "0.5.65"
},
"peerDependenciesMeta": {
"pilotswarm-horizon-store": {
Expand All @@ -98,7 +98,7 @@
"devDependencies": {
"@types/node": "^22.0.0",
"@types/pg": "^8.16.0",
"pilotswarm-horizon-store": "0.5.64",
"pilotswarm-horizon-store": "0.5.65",
"typescript": "^5.0.0",
"vitest": "^4.1.0"
},
Expand Down
4 changes: 4 additions & 0 deletions packages/sdk/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,8 @@ export function projectSerializableSessionConfig(
workingDirectory: fullConfig?.workingDirectory,
waitThreshold: fullConfig?.waitThreshold ?? fallbackWaitThreshold,
boundAgentName: fullConfig?.boundAgentName,
boundAgentPackageId: fullConfig?.boundAgentPackageId,
detachedPackageToolPolicy: fullConfig?.detachedPackageToolPolicy,
promptLayering: fullConfig?.promptLayering,
childContract: fullConfig?.childContract,
toolNames: allNames.length ? allNames : undefined,
Expand Down Expand Up @@ -215,6 +217,8 @@ export class PilotSwarmClient {
contextTier: resolvedConfig.contextTier,
systemMessage: resolvedConfig.systemMessage,
boundAgentName: resolvedConfig.boundAgentName,
boundAgentPackageId: resolvedConfig.boundAgentPackageId,
detachedPackageToolPolicy: resolvedConfig.detachedPackageToolPolicy,
promptLayering: resolvedConfig.promptLayering,
childContract: resolvedConfig.childContract,
tools: resolvedConfig.tools,
Expand Down
31 changes: 25 additions & 6 deletions packages/sdk/src/managed-session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1007,10 +1007,11 @@ export class ManagedSession {
"If the user did not explicitly ask for delegation, use your judgment about whether parallel work is actually helpful. " +
"Each agent adds cost, so avoid unnecessary fan-out when delegation was not requested. " +
"For KNOWN user-creatable agents, pass agent_name. The agent's prompt, tools, and task load automatically. " +
"For delegated work that requires a tool but should not hard-code an agent name, pass required_tool; PilotSwarm resolves the unique visible creatable owner and binds its full definition. " +
"You MAY spawn multiple concurrent instances of the same agent_name (e.g. one per bug or per shard); they each get their own conversation. The only caps are the global maximum concurrent sub-agents and the maximum nesting depth. " +
"Sub-agents do NOT auto-terminate when they finish their task \u2014 they stay alive idle, ready for follow-up via message_agent. YOU are responsible for closing each child with complete_agent (graceful), cancel_agent (interrupt), or delete_agent (forceful) when you no longer need it. " +
"Worker-managed system agents are NOT valid spawn_agent targets; if one is missing, the workers likely need to be restarted. " +
"For CUSTOM agents (ad-hoc tasks), pass task instead. " +
"For CUSTOM agents (ad-hoc tasks), pass task instead. Do not attach package-owned names through tool_names; use required_tool so prompt, skills, handler, and startup contract stay together. " +
"Call ps_list_agents to see all available named agents you CAN spawn. " +
"By default, sub-agents inherit the parent's model. " +
"If you want to override the model, call list_available_models first and use only an exact provider:model value returned there. " +
Expand All @@ -1023,6 +1024,10 @@ export class ManagedSession {
type: "string",
description: "Name of a known user-creatable agent to spawn (from ps_list_agents). The agent's system message, tools, and initial prompt are loaded automatically. Do NOT also pass task or system_message. Worker-managed system agents are not valid here.",
},
required_tool: {
type: "string",
description: "Generic capability selector. Resolve the unique visible creatable named agent declaring this tool, bind its complete definition, and require this tool during bootstrap. Use with task for delegated package-tool work. Ambiguous or missing ownership fails closed.",
},
task: {
type: "string",
description: "For custom agents only: a clear description of what the sub-agent should do. This becomes the agent's first prompt. Do NOT use this for known agents — use agent_name instead.",
Expand Down Expand Up @@ -2117,10 +2122,11 @@ export class ManagedSession {
"Spawn a sub-agent. For KNOWN user-creatable agents, pass agent_name ONLY. " +
"The agent's system message, tools, and initial prompt are loaded automatically from agent_name. " +
"Do NOT pass task or system_message when using agent_name. " +
"For delegated work that requires a tool but should not hard-code an agent name, pass required_tool; PilotSwarm resolves the unique visible creatable owner and binds its full definition. " +
"Calling spawn_agent does NOT finish your turn. After it succeeds, continue executing the rest of your workflow in the SAME turn unless you intentionally call wait, wait_for_agents, ask_user, or give your final answer. " +
"Call ps_list_agents to see all available named agents you CAN spawn. " +
"Worker-managed system agents are not valid spawn_agent targets; if one is missing, the workers likely need to be restarted. " +
"For CUSTOM agents (ad-hoc tasks), pass task instead — no agent_name is needed. " +
"For CUSTOM agents (ad-hoc tasks), pass task instead — no agent_name is needed. Do not attach package-owned names through tool_names; use required_tool so prompt, skills, handler, and startup contract stay together. " +
"Any task you can describe can be spawned as a custom agent; you do not need a skill or pre-configured definition. " +
"If you want a different model, call list_available_models first and use only an exact provider:model value from that list. " +
"If you want different reasoning power, also use only a reasoning_effort value listed for that model. " +
Expand All @@ -2132,6 +2138,10 @@ export class ManagedSession {
type: "string",
description: "Name of a known user-creatable agent to spawn (from ps_list_agents). The agent's prompt, tools, and task load automatically. Do NOT also pass task or system_message. Worker-managed system agents are not valid here.",
},
required_tool: {
type: "string",
description: "Generic capability selector. Resolve the unique visible creatable named agent declaring this tool, bind its complete definition, and require this tool during bootstrap. Use with task for delegated package-tool work. Ambiguous or missing ownership fails closed.",
},
task: {
type: "string",
description: "For custom agents only: a clear description of what the sub-agent should do. Any task can be spawned — no pre-configured agent or skill is required.",
Expand Down Expand Up @@ -2169,10 +2179,14 @@ export class ManagedSession {
},
},
},
handler: async (args: { agent_name?: string; task?: string; model?: string; reasoning_effort?: ReasoningEffort; context_tier?: ContextTier; system_message?: string; tool_names?: string[]; title?: string; contract?: Record<string, unknown> }) => {
handler: async (args: { agent_name?: string; required_tool?: string; task?: string; model?: string; reasoning_effort?: ReasoningEffort; context_tier?: ContextTier; system_message?: string; tool_names?: string[]; title?: string; contract?: Record<string, unknown> }) => {
if (hasTerminalTurnBoundary(turnState)) return blockedAfterTurnBoundary("spawn_agent");
if (!args.agent_name && !args.task) {
return "Error: either agent_name or task is required.";
const requiredTool = typeof args.required_tool === "string" ? args.required_tool.trim() : "";
if (args.required_tool !== undefined && (!requiredTool || requiredTool.length > 128)) {
return "Error: required_tool must be a non-empty tool name of at most 128 characters.";
}
if (!args.agent_name && !args.task && !requiredTool) {
return "Error: agent_name, required_tool, or task is required.";
}
const reasoningEffort = args.reasoning_effort ? normalizeReasoningEffort(args.reasoning_effort) : undefined;
if (args.reasoning_effort && !reasoningEffort) {
Expand All @@ -2182,7 +2196,11 @@ export class ManagedSession {
return "Error: context_tier must be one of default, long_context.";
}
if (controlBridge) {
return await controlBridge.spawnAgent({ ...args, ...(reasoningEffort ? { reasoning_effort: reasoningEffort } : {}) });
return await controlBridge.spawnAgent({
...args,
...(requiredTool ? { required_tool: requiredTool } : {}),
...(reasoningEffort ? { reasoning_effort: reasoningEffort } : {}),
});
}
turnState.pendingActions.push({
type: "spawn_agent",
Expand All @@ -2193,6 +2211,7 @@ export class ManagedSession {
systemMessage: args.system_message,
toolNames: args.tool_names,
agentName: args.agent_name,
requiredTool: requiredTool || undefined,
title: typeof args.title === "string" && args.title.trim() ? args.title.trim() : undefined,
contract: args.contract,
});
Expand Down
6 changes: 4 additions & 2 deletions packages/sdk/src/orchestration-registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ import { durableSessionOrchestration_1_0_69 } from "./orchestration_1_0_69/index
import { durableSessionOrchestration_1_0_70 } from "./orchestration_1_0_70/index.js";
import { durableSessionOrchestration_1_0_71 } from "./orchestration_1_0_71/index.js";
import { durableSessionOrchestration_1_0_72 } from "./orchestration_1_0_72/index.js";
import { durableSessionOrchestration_1_0_73 } from "./orchestration/index.js";
import { durableSessionOrchestration_1_0_73 } from "./orchestration_1_0_73/index.js";
import { durableSessionOrchestration_1_0_74 } from "./orchestration/index.js";

export const DURABLE_SESSION_ORCHESTRATION_NAME = "durable-session-v2";
export { DURABLE_SESSION_LATEST_VERSION } from "./orchestration-version.js";
Expand Down Expand Up @@ -60,5 +61,6 @@ export const DURABLE_SESSION_ORCHESTRATION_REGISTRY: ReadonlyArray<{
{ version: "1.0.70", handler: durableSessionOrchestration_1_0_70 },
{ version: "1.0.71", handler: durableSessionOrchestration_1_0_71 },
{ version: "1.0.72", handler: durableSessionOrchestration_1_0_72 },
{ version: DURABLE_SESSION_LATEST_VERSION, handler: durableSessionOrchestration_1_0_73 },
{ version: "1.0.73", handler: durableSessionOrchestration_1_0_73 },
{ version: DURABLE_SESSION_LATEST_VERSION, handler: durableSessionOrchestration_1_0_74 },
];
2 changes: 1 addition & 1 deletion packages/sdk/src/orchestration-version.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,5 @@
*
* @internal
*/
export const DURABLE_SESSION_LATEST_VERSION = "1.0.73";
export const DURABLE_SESSION_LATEST_VERSION = "1.0.74";
export const DURABLE_SESSION_COMPATIBILITY_FLOOR_VERSION = "1.0.47";
2 changes: 1 addition & 1 deletion packages/sdk/src/orchestration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,5 +42,5 @@ export const CURRENT_ORCHESTRATION_VERSION = DURABLE_SESSION_LATEST_VERSION;
*/

export {
durableSessionOrchestration_1_0_73,
durableSessionOrchestration_1_0_74,
} from "./orchestration/index.js";
Loading
Loading