Skip to content
Merged
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
132 changes: 132 additions & 0 deletions devlog/_plan/260827_kiro_subagent_delegation_unblock/000_plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
# Kiro subagent delegation — unblock parallel spawning

Unit: `260827_kiro_subagent_delegation_unblock` · opened 2026-08-27

## Owner decision that scopes this unit

Two things that look like defects are deliberate and stay:

- The **1024-char description cap** for unverified Kiro models is intentional.
Raising it broke Kiro before (see "Why the cap exists"), so this unit does not
raise it as a blanket change.
- The **5-model `spawn_agent` override list** is intentional
(`MAX_MODEL_OVERRIDES_IN_SPAWN_AGENT = 5`). Not a bug, not in scope.

What the owner *does* want: **Kiro subagents allowed to spawn in parallel,
without a depth ceiling.** That is the goal of this unit.

## Symptom (measured, not inferred)

A top-level `kiro/claude-opus-5` task reports that no delegation tool exists:

> No spawn/subagent tool exists in my current tool catalog.

It then lists a 26-name top-level surface with no `spawn_agent` in it. The tool
is in fact registered — a forced `ALL_TOOLS` probe in the same session found
`multi_agent_v1__spawn_agent` at 6598 chars with `model`, `reasoning_effort`,
and `service_tier` all intact.

So the capability is present and the model cannot see it.

## Mechanism

> **Superseded by the P-phase re-read. See `011_root_cause_nudge_gap.md`.**
> The truncation described below is REAL and measured, but it is the second
> line of defense, not the cause. The actual cause is that
> `src/adapters/kiro.ts:477` omits the optional `codeModeExecName` argument, so
> `codeModeExecWireName()` returns `undefined` and Kiro receives a generic
> fallback sentence that never names `ALL_TOOLS` — regardless of any cap.
> This section is retained because the truncation evidence is what located the
> gap, and because the cap would re-break discovery if the nudge ever regressed.

Codex code mode does not advertise `spawn_agent` at the top level. It defers the
nested tools and tells the model where to look, inside the `exec` tool
description:

> Some deferred nested tools may be omitted from this description. They are
> still available on the global `tools` object and listed in `ALL_TOOLS`.

That sentence sits past the 1024-char mark. `toolDescriptionLimit()` in
`src/adapters/kiro-tools.ts:143` gives 9216 chars to `gpt-5.6-sol` and 1024 to
every other Kiro model, so a Claude-family Kiro model receives `exec` truncated
at exactly 1024 chars ending in `…`.

Confirmed from inside the Kiro session:

| probe | Kiro model sees | control (native) |
|---|---|---|
| `exec` description length | ~1010-1020, ends `…` | full |
| contains `ALL_TOOLS` | NO | YES |
| contains `Some deferred nested tools…` | NO | YES |
| contains `Shared MCP Types` / `declare const tools` | NO | YES |

The failure chain, as corrected at P:

```text
kiro.ts:477 omits codeModeExecName
-> codeModeExecWireName() returns undefined
-> generic fallback replaces the ALL_TOOLS sentence in the system nudge
-> (and independently: exec's own description is cut at 1024, removing it there too)
-> model concludes no spawn tool exists
-> zero delegation attempts, no model/effort/tier override ever formed
```

The model is not misreading the spawn schema. It never learns the schema is
reachable.

## Why the cap exists (do not regress this)

`devlog/_fin/143_kiro-gateway-parity/125_phase_tool_fallback_hardening.md`
originally moved long descriptions **into the system prompt** and left a pointer
in the tool spec. That was later replaced: `tests/kiro-adapter.test.ts:690` is
now named *"tool descriptions use deterministic model-specific caps **without
prompt injection**"* and asserts `current.content` does **not** contain
`### Tool documentation`.

So there are two rejected designs already on record:

1. Send the full long description to Kiro — the reason the cap was added.
2. Relocate it into the prompt — explicitly reverted; the test now guards
against it.

Any proposal in this unit that reintroduces either is out of bounds.

## Design constraint that follows

> **Superseded.** This section reasoned toward a truncation-strategy fix inside
> `kiro-tools.ts`. The implemented direction is `011`: pass `codeModeExecName`
> at the nudge call site, which delivers the discovery contract as system text
> and never touches the cap.

The reasoning is kept because its conclusion still holds and still constrains
the unit: the load-bearing content is ONE sentence (`ALL_TOOLS` discovery), not
the 5.5 KB of type declarations around it. That is why a system-nudge delivery
works — it carries the sentence without carrying the payload.

It also names the residual risk `012` records: the nudge is truncated from the
TAIL under budget pressure, and the code-mode sentence sits late in it. Today
there is ~10 KB of headroom, so the risk is dormant, not absent.

## Work phases

| phase | file | scope |
|---|---|---|
| wp1 | **`011_root_cause_nudge_gap.md`** | pass `codeModeExecName` at Kiro's nudge call site so the shipped `ALL_TOOLS` sentence reaches Kiro |
| wp2 | `020_unbounded_kiro_delegation.md` | remove the depth-1 ceiling for Kiro parents (host config, not code) |
| wp3 | `030_verification.md` | live proof + full gates + PR against `dev` |

`010_exec_discovery_preservation.md` is **superseded** by `011`. It is retained
for its reasoning about why the description tail matters, which is what led to
finding the real call-site gap. Do not implement from `010`.

wp1 is a prerequisite for wp2. Lifting the depth ceiling changes nothing while
the parent still believes the tool does not exist.

## Non-goals

- Raising `MAX_KIRO_TOOL_DESCRIPTION_UNVERIFIED` as a blanket change.
- Changing `MAX_MODEL_OVERRIDES_IN_SPAWN_AGENT`.
- Reintroducing tool documentation into the Kiro system prompt.
- Touching `multi_agent_version` catalog pins — `kiro/claude-opus-5` already
pins `v1` correctly in both `~/.codex/models_cache.json` and
`~/.codex/opencodex-catalog.json`.
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
# wp1 — restore `ALL_TOOLS` discovery for Kiro code-mode turns

Unit: `260827_kiro_subagent_delegation_unblock` · work-phase `wp1`

> **P-phase amendment, 2026-08-27.** The original draft of this document assumed
> the fix had to be a truncation-strategy change inside `truncateDescription()`.
> Re-reading the tree at P found the discovery sentence **already written and
> already shipped** in `src/adapters/tool-catalog-nudge.ts:122`, injected as a
> system nudge that bypasses the 1024 cap entirely — and found that Kiro is the
> one adapter that never enables it. The root cause moved one file; the fix got
> much smaller. The superseded preserve-tail design is kept below as the
> rejected alternative, because the reasoning about *why* the tail matters is
> what led here.

## The one sentence that must arrive

```text
Some deferred nested tools may be omitted from this description. They are
still available on the global `tools` object and listed in `ALL_TOOLS`.
```

~150 chars. Everything the parent needs to discover `spawn_agent` is in it.
A Kiro Claude model currently receives none of it, because the cap keeps the
first 1024 chars of a 5.5 KB description and this sentence is not in that
window.

## Why head-truncation is the wrong shape here

`truncateDescription()` (`src/adapters/kiro-tools.ts:150`) is a pure prefix
slice with a surrogate-safe boundary. It is correct as a byte-safety routine
and wrong as a *documentation* routine: it assumes the first N chars are the
most valuable N chars.

For `exec` that assumption inverts. The head is prose framing ("Run JavaScript
code to orchestrate/compose tool calls"), which the model can infer from the
tool name. The tail carries the discovery contract and the type declarations,
which it cannot infer at all.

## Candidate approaches

### A. Preserve-tail truncation for `exec` only — recommended

Keep the cap. Change *which* 1024 chars survive for the code-mode tool: keep a
head budget for framing, then append the discovery sentence verbatim.

Sketch:

```ts
const KIRO_DISCOVERY_HINT =
"Deferred nested tools are omitted here; they are callable as "
+ "`await tools.<name>(...)` and enumerable via the `ALL_TOOLS` global.";
```

When a description exceeds the limit **and** the original text contains
`ALL_TOOLS`, reserve the hint's length, head-truncate to `limit - hint.length`,
and append. Output stays exactly at the cap, so the wire-size property the cap
exists to protect is unchanged.

- Payload size: identical to today (still capped at 1024).
- Blast radius: only descriptions that mention `ALL_TOOLS` — in practice the
single code-mode `exec` tool.
- Does not touch the prompt, so `tests/kiro-adapter.test.ts:700`
(`not.toContain("### Tool documentation")`) stays green.

### B. Per-tool allowlist limit

Give `exec` a larger cap while every other unverified-model tool stays at 1024.
Simple, but it is a narrower version of the change that previously broke Kiro,
and it needs a live probe per model family to establish the real ceiling. Keep
as fallback if A proves insufficient.

### C. Do nothing in the adapter; fix upstream ordering

Ask for the discovery sentence to move to the top of the `exec` description.
Correct in principle, out of this repository's control, and leaves every
shipped Codex version broken. Rejected as the primary path.

## Chosen path

**A**, with **B** held in reserve.

## Implementation targets

| file | change |
|---|---|
| `src/adapters/kiro-tools.ts` | add discovery-preserving branch inside the truncation path |
| `tests/kiro-adapter.test.ts` | regression next to the existing cap test at :690 |

## Regression contract

The new test must assert all four, or it is not proving the fix:

1. A >1024-char description containing `ALL_TOOLS` still yields
`spec.description.length === 1024` for an unverified model.
2. That output **contains** `ALL_TOOLS`.
3. A >1024-char description **without** `ALL_TOOLS` is byte-identical to
today's behavior (plain head-truncate ending in `…`).
4. `current.content` still does not contain `### Tool documentation`.

Assertion 3 is what keeps this from becoming a general cap change. Drive the
test red once before wiring the fix — a preserve-tail assertion passes
vacuously if the fixture description happens to be under the cap.

## Verification

```bash
bun x tsc --noEmit
bun test tests/kiro-adapter.test.ts
```

Focused per `AGENTS.md`: this is a scoped adapter change, not shared runtime.

## Exit criterion

A live `kiro/claude-opus-5` task, asked what tools it has, names
`multi_agent_v1__spawn_agent` **without being told where to look**. Today the
same prompt returns "No spawn/subagent tool exists in my current tool catalog."
Loading
Loading