external-subagent 0.2.0 → 0.3.1

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
@@ -8,10 +8,10 @@ independent axes:
8
8
  - **Hosts** — Codex (`install-plugin codex` or `install-mcp`), ZCode
9
9
  (`install-plugin zcode`), and any custom local MCP client that connects
10
10
  directly to the facade without registration.
11
- - **Subagents** — ZCode, DeepSeek Harness (DSH), and Codex, each routed
12
- through an internal adapter and subject to the per-subagent limitations
13
- listed in [docs/product.md](docs/product.md) and
14
- [docs/mcp-api.md](docs/mcp-api.md).
11
+ - **Subagents** — ZCode, DeepSeek Harness (DSH), Codex, and Google
12
+ Antigravity (`agy`), each routed through an internal adapter and subject to
13
+ the per-subagent limitations listed in [docs/product.md](docs/product.md)
14
+ and [docs/mcp-api.md](docs/mcp-api.md).
15
15
 
16
16
  ## Status
17
17
 
@@ -26,9 +26,9 @@ artifact and explicitly initializing to a launchd-resident service
26
26
  active-task upgrade and standalone initialization are implemented and
27
27
  reviewed.
28
28
 
29
- All three subagents are **disabled by default** and are enabled per name with
30
- `external-subagent agents enable <zcode|dsh|codex>`: a successful local probe
31
- writes the configuration (no write on failure), and `dsh`/`codex` need a
29
+ All four subagents are **disabled by default** and are enabled per name with
30
+ `external-subagent agents enable <zcode|dsh|codex|agy>`: a successful local probe
31
+ writes the configuration (no write on failure), and `dsh`/`codex`/`agy` need a
32
32
  daemon restart to take effect. ZCode admits all four permission modes
33
33
  (build/edit/plan/yolo) and rejects an explicit spawn `model`; DSH admits
34
34
  `build` and strict `plan`, with model selection as explicit spawn model,
@@ -40,9 +40,16 @@ legacy build composition and `plan` still requires an empty manifest); Codex
40
40
  admits all four modes
41
41
  (build/edit → workspace-write, plan → read-only, yolo → danger-full-access,
42
42
  all pinning `approvalPolicy=never`) and rejects a non-empty `write_manifest`
43
- with `codex_write_manifest_unsupported`. `observe` works on all three: ZCode
43
+ with `codex_write_manifest_unsupported`; Antigravity (`agy`) spawns only when
44
+ its `enabled + spawn_supported` gate and an absolute `AGY_RUNTIME_PATH`
45
+ executable admit it, runs with `build` (`--mode accept-edits`) or `yolo`
46
+ (`--dangerously-skip-permissions`), selects a catalog-validated bare model
47
+ slug, admits effort from `low|medium|high|max`, and rejects a non-empty
48
+ `write_manifest` with `agy_write_manifest_unsupported` (it has no
49
+ permission-respond interaction — tools are soft-denied and denied actions
50
+ surface on failure diagnostics). `observe` works on all four: ZCode
44
51
  and DSH expose the public reasoning tail (at most 200 characters) and Codex
45
- reports `reasoning: null`.
52
+ and agy report `reasoning: null`.
46
53
 
47
54
  The daemon socket environment variable is now the neutral
48
55
  `EXTERNAL_SUBAGENT_SOCKET`, with no fallback to the retired name. An existing
@@ -65,7 +65,7 @@ export function parseSubagentsArgs(args) {
65
65
  const [operation, ...rest] = args;
66
66
  if (!['list', 'status', 'probe', 'models', 'enable'].includes(operation)) throw new CliError('INVALID_ARGUMENT', `unsupported subagents operation: ${operation}`, 2);
67
67
  if (operation === 'enable') {
68
- if (rest.length !== 1 || !SUBAGENT_IDS.includes(rest[0])) throw new CliError('INVALID_ARGUMENT', 'usage: agents enable <zcode|dsh|codex>', 2);
68
+ if (rest.length !== 1 || !SUBAGENT_IDS.includes(rest[0])) throw new CliError('INVALID_ARGUMENT', 'usage: agents enable <zcode|dsh|codex|agy>', 2);
69
69
  return { operation, subagent: rest[0] };
70
70
  }
71
71
  if (operation === 'probe') return parseProbeArgs(rest);
@@ -82,7 +82,7 @@ export async function subagentsCommand(paths, input = {}, options = {}) {
82
82
  if (operation === 'enable') {
83
83
  const agent = input.subagent;
84
84
  if (!SUBAGENT_IDS.includes(agent) || ['through', 'workspace', 'home'].some((key) => input[key] !== undefined)) {
85
- throw new CliError('INVALID_ARGUMENT', 'usage: agents enable <zcode|dsh|codex>', 2);
85
+ throw new CliError('INVALID_ARGUMENT', 'usage: agents enable <zcode|dsh|codex|agy>', 2);
86
86
  }
87
87
  if (typeof options.callDaemon !== 'function' || typeof options.socket !== 'string') throw new CliError('INTERNAL_ERROR', 'agents enable requires a daemon connection');
88
88
  const entry = config.subagents[agent];
@@ -102,9 +102,15 @@ export async function subagentsCommand(paths, input = {}, options = {}) {
102
102
  if (typeof local.runtime_path !== 'string' || !path.isAbsolute(local.runtime_path) || typeof local.version !== 'string' || !local.version.trim()) {
103
103
  throw new CliError('agent_probe_failed', 'Probe did not return an absolute runtime_path and observed version', 2);
104
104
  }
105
- const home = local.scope?.home;
106
- if (typeof home !== 'string' || !path.isAbsolute(home)) throw new CliError('agent_probe_failed', `Cannot enable ${agent}: configure subagents.${agent}.home or export ${agent.toUpperCase()}_HOME before starting the daemon`, 2);
107
- Object.assign(patch, { runtime_path: local.runtime_path, home, profile: agent === 'dsh' ? 'acp' : (entry.profile ?? null), version: local.version });
105
+ if (agent === 'agy') {
106
+ // agy has no home override: the daemon resolves the executable from
107
+ // AGY_RUNTIME_PATH, so the patch carries runtime_path + version only.
108
+ Object.assign(patch, { runtime_path: local.runtime_path, version: local.version });
109
+ } else {
110
+ const home = local.scope?.home;
111
+ if (typeof home !== 'string' || !path.isAbsolute(home)) throw new CliError('agent_probe_failed', `Cannot enable ${agent}: configure subagents.${agent}.home or export ${agent.toUpperCase()}_HOME before starting the daemon`, 2);
112
+ Object.assign(patch, { runtime_path: local.runtime_path, home, profile: agent === 'dsh' ? 'acp' : (entry.profile ?? null), version: local.version });
113
+ }
108
114
  }
109
115
  const updated = updateConfig(paths.config, (latest) => {
110
116
  if (Object.entries(patch).every(([key, value]) => latest.subagents[agent][key] === value)) return latest;
@@ -1,7 +1,7 @@
1
1
  import { CliError } from '../errors.mjs';
2
2
 
3
3
  export const CONFIG_SCHEMA_VERSION = 2;
4
- export const SUBAGENT_IDS = Object.freeze(['zcode', 'dsh', 'codex']);
4
+ export const SUBAGENT_IDS = Object.freeze(['zcode', 'dsh', 'codex', 'agy']);
5
5
  const CONFIG_FIELDS = new Set(['schema_version', 'revision', 'default_subagent', 'subagents']);
6
6
  const AGENT_FIELDS = new Set(['enabled', 'spawn_supported', 'default_model', 'runtime_path', 'home', 'profile', 'version']);
7
7
 
@@ -24,6 +24,7 @@ export function defaultConfig() {
24
24
  zcode: { enabled: false, spawn_supported: false, default_model: null },
25
25
  dsh: { enabled: false, spawn_supported: false, default_model: null, runtime_path: null, home: null, profile: null, version: null },
26
26
  codex: { enabled: false, spawn_supported: false, default_model: null, runtime_path: null, home: null, profile: null, version: null },
27
+ agy: { enabled: false, spawn_supported: false, default_model: null, runtime_path: null, home: null, profile: null, version: null },
27
28
  },
28
29
  };
29
30
  }
package/cli/constants.mjs CHANGED
@@ -8,7 +8,7 @@ const IS_DEBUG_VARIANT = process.env.EXTERNAL_SUBAGENT_VARIANT === 'debug';
8
8
 
9
9
  export const PRODUCT_NAME = IS_DEBUG_VARIANT ? 'external-subagent-debug' : 'external-subagent';
10
10
  export const PRODUCT_ID = IS_DEBUG_VARIANT ? 'external_subagent_debug' : 'external_subagent';
11
- export const VERSION = '0.2.0';
11
+ export const VERSION = '0.3.1';
12
12
  export const LAUNCH_AGENT_LABEL = IS_DEBUG_VARIANT ? 'com.external-subagent-debug.daemon' : 'com.external-subagent.daemon';
13
13
  export const DAEMON_BIN_NAME = IS_DEBUG_VARIANT ? 'external-subagent-debugd' : 'external-subagentd';
14
14
  export const MCP_BIN_NAME = IS_DEBUG_VARIANT ? 'external-subagent-debug-mcp' : 'external-subagent-mcp';
@@ -21,7 +21,7 @@ function escapeXml(value) {
21
21
  // Presence is diagnostic only; enabling is an explicit probe + config operation.
22
22
  export function runtimeObservations(paths, options = {}) {
23
23
  const config = readConfig(paths.config);
24
- return Object.fromEntries(['zcode', 'dsh', 'codex'].map((agent) => {
24
+ return Object.fromEntries(['zcode', 'dsh', 'codex', 'agy'].map((agent) => {
25
25
  const runtime = agent === 'zcode' ? (options.zcodeRuntime ?? ZCODE_RUNTIME) : config.subagents[agent].runtime_path;
26
26
  return [agent, { path: runtime, present: Boolean(runtime && fs.existsSync(runtime)), enabled: config.subagents[agent].enabled }];
27
27
  }));
@@ -35,6 +35,7 @@ export function launchAgentPlist(paths, options = {}) {
35
35
  const config = readConfig(paths.config);
36
36
  const { runtime_path: dshRuntime, home: dshHome, profile: dshProfile, version: dshVersion } = config.subagents.dsh;
37
37
  const { runtime_path: codexRuntime, home: codexHome } = config.subagents.codex;
38
+ const { runtime_path: agyRuntime } = config.subagents.agy;
38
39
  const configRevision = config.revision;
39
40
  // AUD-005/D1: the standalone service never depends on an unrelated runtime.
40
41
  // The pinned ZCode runtime is forwarded only when that installation exists;
@@ -70,6 +71,10 @@ export function launchAgentPlist(paths, options = {}) {
70
71
  // fails Codex admission closed instead of falling back to ~/.codex.
71
72
  ...(codexRuntime ? [`<key>CODEX_RUNTIME_PATH</key><string>${escapeXml(codexRuntime)}</string>`] : []),
72
73
  ...(codexHome ? [`<key>CODEX_HOME</key><string>${escapeXml(codexHome)}</string>`] : []),
74
+ // agy has no home override: only its persisted executable is forwarded, so
75
+ // the service-side daemon resolves the same AGY_RUNTIME_PATH the
76
+ // interactive one does.
77
+ ...(agyRuntime ? [`<key>AGY_RUNTIME_PATH</key><string>${escapeXml(agyRuntime)}</string>`] : []),
73
78
  ].join('');
74
79
  return Buffer.from(`<?xml version="1.0" encoding="UTF-8"?>\n<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">\n<plist version="1.0"><dict>\n<key>Label</key><string>${LAUNCH_AGENT_LABEL}</string>\n<key>ProgramArguments</key><array>${programArguments.join('')}</array>\n<key>EnvironmentVariables</key><dict>${dshEnvironment}</dict>\n<key>RunAtLoad</key><true/><key>KeepAlive</key><true/>\n<key>StandardOutPath</key><string>${escapeXml(path.join(paths.logs, 'daemon.log'))}</string>\n<key>StandardErrorPath</key><string>${escapeXml(path.join(paths.logs, 'daemon-error.log'))}</string>\n</dict></plist>\n`);
75
80
  }
package/cli/rpc.mjs CHANGED
@@ -7,6 +7,7 @@ export const MAX_FRAME_BYTES = 512 * 1024;
7
7
  export const MAX_RESPONSE_FRAME_BYTES = 2 * 1024 * 1024;
8
8
  export const MAX_RESULT_CHUNK_BYTES = 256 * 1024;
9
9
  export const AGENT_PROBE_TRANSPORT_TIMEOUT_MS = 200_000;
10
+ export const SPAWN_TRANSPORT_TIMEOUT_MS = 150_000;
10
11
  export const MIN_TASK_ID = 10_000_000;
11
12
  export const MAX_TASK_ID = 99_999_999;
12
13
 
@@ -38,6 +39,7 @@ export function waitTransportTimeoutMs(waitTime = 290) {
38
39
  export function daemonTransportTimeoutMs(command, params = {}) {
39
40
  if (command === 'wait') return waitTransportTimeoutMs(params.wait_time);
40
41
  if (command === 'agent-probe' || command === 'agent-models') return AGENT_PROBE_TRANSPORT_TIMEOUT_MS;
42
+ if (command === 'create' || command === 'spawn') return SPAWN_TRANSPORT_TIMEOUT_MS;
41
43
  return 6000;
42
44
  }
43
45
 
@@ -136,8 +138,10 @@ function methodFor(command, input) {
136
138
  } };
137
139
  }
138
140
  case 'send': {
139
- rejectUnknownFields('send', input, new Set(['agent_id', 'message_id', 'content']));
141
+ rejectUnknownFields('send', input, new Set(['agent_id', 'message_id', 'mode', 'content']));
142
+ if (!['queue', 'steer'].includes(input.mode)) throw new CliError('VALIDATION', 'send mode must be queue or steer', 2);
140
143
  return { method: 'task_message', params: {
144
+ mode: input.mode,
141
145
  agent_id: daemonTaskId(input.agent_id),
142
146
  ...(input.message_id ? { message_id: input.message_id } : {}),
143
147
  content: input.content,
@@ -174,6 +178,16 @@ function publicTask(task) {
174
178
  };
175
179
  }
176
180
 
181
+ // The wait response carries only the task lifecycle header: admission
182
+ // provenance never changes after spawn, so wait does not repeat it.
183
+ function publicTaskHeader(task) {
184
+ return {
185
+ agent_id: publicTaskId(task.agent_id),
186
+ status: task.status,
187
+ session_id: task.session_id ?? null,
188
+ };
189
+ }
190
+
177
191
  function publicResult(result) {
178
192
  if (result == null) return null;
179
193
  return {
@@ -195,10 +209,10 @@ export function projectDaemonResult(command, result) {
195
209
  case 'agent-probe': return { evidence: result.evidence, status: result.status };
196
210
  case 'agent-models': return result.catalog ?? result;
197
211
  case 'create': case 'spawn':
198
- return { agent_id: publicTaskId(result.task.agent_id), status: result.task.status };
212
+ return { agent_id: publicTaskId(result.task.agent_id), status: result.task.status, session_id: result.task.session_id ?? null };
199
213
  case 'wait': {
200
214
  const { result: taskResult, task, activity, kind: _kind, ...rest } = result;
201
- return { ...rest, task: publicTask(task), activity, result: publicResult(taskResult) };
215
+ return { ...rest, task: publicTaskHeader(task), activity, result: publicResult(taskResult) };
202
216
  }
203
217
  case 'list': return { tasks: result.tasks.map(publicTask), next_cursor: result.next_cursor ?? null };
204
218
  case 'send': return { message_id: result.message_id, disposition: result.disposition };
@@ -1,20 +1,20 @@
1
1
  {
2
2
  "schema_version": 1,
3
3
  "product": "external-subagent",
4
- "version": "0.2.0",
4
+ "version": "0.3.1",
5
5
  "platform": "darwin-arm64",
6
6
  "files": [
7
7
  {
8
8
  "name": "external-subagentd",
9
- "bytes": 9529168,
9
+ "bytes": 9911616,
10
10
  "mode": "755",
11
- "sha256": "3ad4a342178a1550ee8c10614493d7a4f2c45588ca1a8409397fdd04e123f343"
11
+ "sha256": "d3f6d45a32d7675ba68fa059e70cca4c85f1ed781441b53dddfa7585751ea339"
12
12
  },
13
13
  {
14
14
  "name": "external-subagent-mcp",
15
- "bytes": 937840,
15
+ "bytes": 935760,
16
16
  "mode": "755",
17
- "sha256": "e3ccf595590286c5718a35eeefa6ff08d0549a3acf05a828f96b98ed713b5e70"
17
+ "sha256": "504256978a7cf0cc16b13c8f53d75bca3c53bf65d1286a3c9bc22dc923cd0476"
18
18
  }
19
19
  ]
20
20
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "external-subagent",
3
- "version": "0.2.0",
4
- "description": "Manage bounded ZCode, DeepSeek Harness (DSH), and Codex subagent tasks through a local MCP service for any local host (Codex, ZCode, or a custom MCP client)",
3
+ "version": "0.3.1",
4
+ "description": "Manage bounded ZCode, DeepSeek Harness (DSH), Codex, and Google Antigravity (agy) subagent tasks through a local MCP service for any local host (Codex, ZCode, or a custom MCP client)",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "repository": {
@@ -52,6 +52,7 @@
52
52
  "postpack": "node scripts/release/check-native-tarball.mjs",
53
53
  "prepublishOnly": "node scripts/release/check-native-tarball.mjs --staged",
54
54
  "test:install": "node --test tests/install/*.test.mjs",
55
+ "test:affected": "node scripts/test/affected.mjs",
55
56
  "check": "node --check bin/external-subagent.mjs && node --check bin/external-subagent-mcp.mjs && node --check bin/external-subagent-debug.mjs && node --check bin/external-subagent-debug-mcp.mjs && node --check cli/main.mjs"
56
57
  },
57
58
  "engines": {
@@ -7,7 +7,7 @@ description: Manage bounded repository-scoped tasks through external-subagent.
7
7
 
8
8
  Use the `external_subagent_*` tools to inspect status, spawn a task with an explicit
9
9
  `subagent`, wait for progress, retrieve the complete result, and close the task.
10
- Execution targets are `zcode`, `dsh`, and `codex`; each name currently has one
10
+ Execution targets are `zcode`, `dsh`, `codex`, and `agy`; each name currently has one
11
11
  runtime instance and no instance selector is supported. Omitting `subagent` uses
12
12
  `default_subagent` or returns `subagent_required`; unknown targets return
13
13
  `subagent_unknown`, and unknown spawn fields are rejected before dispatch.
@@ -22,10 +22,11 @@ evidence are operator diagnostics — read them with the CLI `diagnose` command
22
22
  instead of the MCP status tool.
23
23
 
24
24
  Spawn accepts an optional `effort` token that steers the task's reasoning
25
- effort. Omit it to keep each agent's current default (codex keeps its existing
26
- wire default; zcode and dsh send no effort field or call at all). `codex`
27
- admits only the closed set
25
+ effort. Omit it to keep each agent's current default (codex and agy keep their
26
+ existing wire default; zcode and dsh send no effort field or call at all).
27
+ `codex` admits only the closed set
28
28
  `low | medium | high | xhigh | max` — `minimal` and `ultra` are rejected;
29
+ `agy` admits only the measured closed set `low | medium | high | max`;
29
30
  `zcode` and `dsh` accept any bounded token (1..24 bytes of `[a-z0-9_]`) and
30
31
  pass it through to the runtime, because the supported set is only known at
31
32
  runtime. A malformed or unsupported token is rejected before dispatch, so no
@@ -40,9 +41,9 @@ Codex home instances, and offers plugin or direct MCP bindings; built-in
40
41
  dirs with a single per-user instance. This is separate
41
42
  from `subagent.codex`. Protocol implementations are adapters.
42
43
 
43
- All three subagents (`zcode`, `dsh`, `codex`) are disabled by default; enable one
44
+ All four subagents (`zcode`, `dsh`, `codex`, `agy`) are disabled by default; enable one
44
45
  with `external-subagent agents enable <name>`, which runs a local probe and writes
45
- the configuration only on success (a daemon restart is required for `dsh`/`codex`).
46
+ the configuration only on success (a daemon restart is required for `dsh`/`codex`/`agy`).
46
47
  Actual per-subagent limitations (from the accepted code):
47
48
 
48
49
  - `zcode` — disabled until explicitly enabled; all four permission modes
@@ -56,7 +57,15 @@ Actual per-subagent limitations (from the accepted code):
56
57
  and `yolo` to sandbox=danger-full-access, all pinning approvalPolicy=never.
57
58
  A non-empty spawn `write_manifest` is rejected before task creation with
58
59
  `codex_write_manifest_unsupported`.
59
- - `observe` — available for all three subagents; `zcode` and `dsh` return the
60
- public reasoning tail (at most 200 characters), while `codex` does not collect
61
- reasoning and returns `reasoning: null`. Tool history and coverage are reported
62
- truthfully per adapter capability.
60
+ - `agy` (Google Antigravity) — disabled until explicitly enabled with an
61
+ absolute executable `AGY_RUNTIME_PATH`; only `build` (`--mode accept-edits`)
62
+ and `yolo` (`--dangerously-skip-permissions`) are admitted (plan/edit are
63
+ rejected with `agy_permission_mode_unsupported`); `model` is a bare slug
64
+ validated against the `agy models` catalog; a non-empty spawn
65
+ `write_manifest` is rejected before task creation with
66
+ `agy_write_manifest_unsupported`; there is no permission-respond interaction
67
+ (tools are soft-denied).
68
+ - `observe` — available for all four subagents; `zcode` and `dsh` return the
69
+ public reasoning tail (at most 200 characters), while `codex` and `agy` do not
70
+ collect reasoning and return `reasoning: null`. Tool history and coverage are
71
+ reported truthfully per adapter capability.
@@ -15,7 +15,7 @@
15
15
  "type": "object",
16
16
  "required": ["subagent", "configured", "enabled", "spawn_supported", "permission_modes", "model_selection", "effort_selection", "local", "auth", "hi"],
17
17
  "properties": {
18
- "subagent": { "enum": ["zcode", "dsh", "codex"] },
18
+ "subagent": { "enum": ["zcode", "dsh", "codex", "agy"] },
19
19
  "configured": { "type": "boolean" },
20
20
  "enabled": { "type": "boolean" },
21
21
  "spawn_supported": { "type": "boolean" },
@@ -60,21 +60,29 @@
60
60
  },
61
61
  "spawn": {
62
62
  "type": "object",
63
+ "description": "Start one durable subagent in an absolute repository workspace, returning after establishing the new session.",
63
64
  "required": ["repository", "prompt"],
64
65
  "properties": {
65
66
  "subagent": { "type": "string", "minLength": 1, "description": "Subagent route key; omission uses configured default_subagent or returns subagent_required. Null is invalid; dsh production spawn requires its enabled + spawn_supported + pinned-runtime configuration." },
66
67
  "repository": { "type": "string", "minLength": 1 },
67
- "permission_mode": { "$ref": "#/properties/permission_mode", "description": "codex supports build/edit (workspace-write), plan (read-only), and yolo (danger-full-access), always with approvalPolicy=never." },
68
+ "permission_mode": { "$ref": "#/properties/permission_mode", "description": "codex supports build/edit (workspace-write), plan (read-only), and yolo (danger-full-access), always with approvalPolicy=never; agy supports only build (--mode accept-edits) and yolo (--dangerously-skip-permissions)." },
68
69
  "prompt": { "type": "string", "minLength": 1 },
69
- "model": { "type": "string", "minLength": 1, "description": "Task model. dsh requires provider:model, split at the first colon (the model side may contain further colons; a missing colon or an empty side is rejected before any task). codex uses its own model id; zcode uses provider/model (a bare token keeps its backward-compatible zai/<token> reading). Omission falls back to the configured default_model for dsh and zcode, then to the provider-native model; codex requires an explicit model or agents.codex.default_model and never falls back to a provider default." },
70
- "effort": { "type": "string", "minLength": 1, "maxLength": 24, "pattern": "^[a-z0-9_]+$", "description": "Reasoning-effort token: 1..24 bytes of [a-z0-9_]. codex admits only low, medium, high or xhigh; zcode and dsh pass the bounded token through. Omission keeps the runtime default." },
71
- "write_manifest": { "type": "array", "items": { "type": "string", "minLength": 1 }, "description": "codex requires an empty or omitted write_manifest; non-empty input returns codex_write_manifest_unsupported before task creation. dsh admits a non-empty manifest in build through the guarded manifest-build composition (workspace-write sandbox, only tool-fs writable, out-of-manifest paths rejected as FS_WRITE_MANIFEST_DENIED), at most 256 entries; an explicit [\".\"] keeps the existing build composition. plan requires an empty manifest for every subagent." }
70
+ "model": { "type": "string", "minLength": 1, "description": "Task model. dsh requires provider:model, split at the first colon (the model side may contain further colons; a missing colon or an empty side is rejected before any task). codex uses its own model id; zcode uses provider/model (a bare token keeps its backward-compatible zai/<token> reading). agy uses a bare model slug with no ':' or '/' separator or whitespace; admission validates only that shape and the daemon does not verify catalog membership, so an unknown slug fails loudly at session start when the agy CLI rejects it. Omission falls back to the configured default_model for dsh, zcode and agy, then to the provider-native model; codex requires an explicit model or agents.codex.default_model and never falls back to a provider default." },
71
+ "effort": { "type": "string", "minLength": 1, "maxLength": 24, "pattern": "^[a-z0-9_]+$", "description": "Reasoning-effort token: 1..24 bytes of [a-z0-9_]. codex admits only low, medium, high or xhigh; agy admits only low, medium, high or max; zcode and dsh pass the bounded token through. Omission keeps the runtime default." },
72
+ "write_manifest": { "type": "array", "items": { "type": "string", "minLength": 1 }, "description": "codex requires an empty or omitted write_manifest; non-empty input returns codex_write_manifest_unsupported before task creation. agy likewise requires an empty or omitted write_manifest; non-empty input returns agy_write_manifest_unsupported before task creation. dsh admits a non-empty manifest in build through the guarded manifest-build composition (workspace-write sandbox, only tool-fs writable, out-of-manifest paths rejected as FS_WRITE_MANIFEST_DENIED), at most 256 entries; an explicit [\".\"] keeps the existing build composition. plan requires an empty manifest for every subagent." }
72
73
  },
73
74
  "allOf": [
74
75
  { "if": { "required": ["subagent"], "properties": { "subagent": { "const": "codex" } } },
75
76
  "then": { "properties": { "write_manifest": { "maxItems": 0 } } } },
76
77
  { "if": { "required": ["subagent"], "properties": { "subagent": { "const": "dsh" } } },
77
78
  "then": { "properties": { "write_manifest": { "maxItems": 256 } } } },
79
+ { "if": { "required": ["subagent"], "properties": { "subagent": { "const": "agy" } } },
80
+ "then": { "properties": {
81
+ "permission_mode": { "enum": ["build", "yolo"] },
82
+ "model": { "type": "string", "minLength": 1, "maxLength": 512, "pattern": "^[^:/\\s]+$" },
83
+ "effort": { "enum": ["low", "medium", "high", "max"] },
84
+ "write_manifest": { "maxItems": 0 }
85
+ } } },
78
86
  { "if": { "required": ["permission_mode"], "properties": { "permission_mode": { "const": "plan" } } },
79
87
  "then": { "properties": { "write_manifest": { "maxItems": 0 } } } }
80
88
  ],
@@ -95,7 +103,7 @@
95
103
  },
96
104
  "wait": {
97
105
  "type": "object",
98
- "description": "Wait returns early only for an actionable pending request (permission requests need allow/deny; user-input requests carry the full embedded question and need answer with non-empty content) or a terminal result; ordinary progress, message receipts, already-responded and unsupported requests never wake it. The pending_requests output projection is capped at 100 actionable records, while the wake decision scans the full pending set. activity carries the text tail incrementally: each wait returns only the text appended since the previous wait (an empty tail means nothing new), every byte is delivered by wait at most once, and latest_text_truncated flags undelivered bytes that rolled out of the bounded window (that response degrades to the whole window); a terminal response also strips bytes repeating the embedded result page verbatim. activity also carries a 200-char verified reasoning tail, tool calls started in the last 60 seconds, and telemetry status.",
106
+ "description": "Wait up to 290 seconds by default. Returns early only when an actionable pending request exists (permission requests need allow/deny; user-input requests carry the full embedded question and need answer with non-empty content) or the terminal result is available; ordinary progress, message receipts, already-responded and unsupported requests never wake it, and its timeout still returns timed_out=true. When the final result is embedded with complete=true no further result call is needed; a partial page directs you to external_subagent_result with next_offset. A failed terminal result also carries failure_message: usually the daemon's bounded single-line JSON failure detail, but an early fail_claim row may be plain text, so treat it as an opaque bounded string and attempt JSON parsing defensively (null when the task has no persisted detail or the outcome is not a failure). Read failure_message before falling back to daemon logs or sqlite. The pending_requests projection stays capped at 100 records while the wake decision scans the full pending set, and the request that woke the wait is always part of the returned projection so its request_id can be answered directly. activity carries the text tail incrementally: each wait returns only the text appended since the previous wait (an empty tail means nothing new), every byte is delivered by wait at most once, and latest_text_truncated flags the case where undelivered bytes rolled out of the bounded window so this response degraded to the whole window (on the terminal response, bytes repeating the embedded result page verbatim are stripped so they are not shipped twice), a 200-char verified reasoning tail, tool calls started in the last 60 seconds, and telemetry status; activity also exposes last_tool_calls as up to three [tool, arg] string pairs in chronological order across turns (tool up to 64 characters, arg up to 512 UTF-8 bytes), last_activity_age_ms (null before any activity), and model_request_active; additionally in_flight_tool_count counts tracked in-flight tool calls (scheduled or started without a matching result, error or permission resolution; 0 when there is no snapshot or the adapter has no per-call lifecycle tracking, such as codex) and oldest_in_flight_tool_age_ms is the age since the oldest tracked in-flight call first appeared (null when none is tracked); use observe only when these suggest a meaningless loop.",
99
107
  "required": ["agent_id"],
100
108
  "properties": {
101
109
  "agent_id": { "type": "integer", "minimum": 10000000, "maximum": 99999999 },
@@ -105,7 +113,7 @@
105
113
  "additionalProperties": false
106
114
  },
107
115
  "observe": {
108
- "description": "Read captured task observations for zcode, dsh or codex. zcode and dsh expose at most 200 Unicode characters of public reasoning; codex reasoning is null. Coverage reports capture gaps and unsupported history truthfully.",
116
+ "description": "Read captured task observations for zcode, dsh, codex or agy. zcode and dsh expose at most 200 Unicode characters of public reasoning; codex and agy reasoning is null. Coverage reports capture gaps and unsupported history truthfully.",
109
117
  "type": "object",
110
118
  "required": ["agent_id"],
111
119
  "properties": {
@@ -175,11 +183,11 @@
175
183
  "additionalProperties": false,
176
184
  "properties": {
177
185
  "external_subagent_status": { "input": [], "output": ["mcp_version", "components", "capabilities", "subagents"] },
178
- "external_subagent_spawn": { "input": ["subagent", "repository", "permission_mode", "prompt", "model", "effort", "write_manifest"], "output": ["agent_id", "status"], "idempotent": false },
186
+ "external_subagent_spawn": { "input": ["subagent", "repository", "permission_mode", "prompt", "model", "effort", "write_manifest"], "output": ["agent_id", "status", "session_id"], "idempotent": false },
179
187
  "external_subagent_wait": { "input": ["agent_id", "wait_time", "message_id"], "output": ["task", "pending_requests", "result_available", "activity", "result", "instruction", "timed_out", "message_receipt"] },
180
188
  "external_subagent_observe": { "input": ["agent_id"], "output": ["tools", "reasoning", "coverage"], "idempotent": true },
181
189
  "external_subagent_list": { "input": ["subagent", "repository", "phase", "outcome", "cursor", "limit"], "output": ["tasks", "next_cursor"] },
182
- "external_subagent_send": { "input": ["agent_id", "message_id", "content"], "output": ["message_id", "disposition"] },
190
+ "external_subagent_send": { "input": ["agent_id", "message_id", "mode", "content"], "required": ["agent_id", "mode", "content"], "mode": ["queue", "steer"], "output": ["message_id", "disposition"] },
183
191
  "external_subagent_respond": { "input": ["agent_id", "request_id", "decision", "content"], "output": ["disposition", "requested_decision", "effective_decision", "policy_overrode", "policy_reason_code"] },
184
192
  "external_subagent_cancel": { "input": ["agent_id"], "output": ["task"] },
185
193
  "external_subagent_result": { "input": ["agent_id", "offset", "limit"], "output": ["task", "result"] },
@@ -31,6 +31,12 @@
31
31
  "status": "HIDDEN",
32
32
  "reasoning": null,
33
33
  "collected": false
34
+ },
35
+ "agy": {
36
+ "public": false,
37
+ "status": "HIDDEN",
38
+ "reasoning": null,
39
+ "collected": false
34
40
  }
35
41
  }
36
42
  }