Skip to content

fix(send): allow_busy opt-in for send_to / send_to_agent - #86

Merged
EtanHey merged 3 commits into
mainfrom
fix/send-to-allow-busy
Apr 23, 2026
Merged

EtanHey merged 3 commits into
mainfrom
fix/send-to-allow-busy

Conversation

@EtanHey

@EtanHey EtanHey commented Apr 23, 2026 •

Copy link
Copy Markdown
Owner

Summary

Adds allow_busy: boolean opt-in to send_to and send_to_agent MCP endpoints. When true, bypasses the not in an interactive state gate in deliverAgentInput and delivers raw keystrokes regardless of agent state (matches send_input behavior). Default unchanged: omitting allow_busy (or passing false) preserves the current gate.

Lets orchestrators interject while an agent is working — cancel, steer, or stack an instruction — without falling back to send_input.

Motivation

Surfaced during session mining on 2026-04-23. When an orchestrator tries send_to_agent to an agent in state=working, the call rejects with not in an interactive state. There's no clean way to stack an instruction without waiting for idle or falling back to send_input (which has its own quirks). Inconsistent with send_input, which already delivers raw keystrokes regardless of agent state.

Design choice

Raw delivery via opt-in flag was preferred over an auto-queue for three reasons:

  • minimal API surface change — single boolean parameter, backwards-compatible default.
  • matches send_input semantics — consistent mental model across the two entrypoints.
  • no hidden ordering guarantees — callers that want FIFO queueing can build it on top; this primitive stays honest.

The error message now points callers at the opt-in so the next time the gate fires, the remediation is self-describing.

TDD evidence

  • Failing-test commit: 53da1b8 (test: failing case for send_to allow_busy opt-in)
  • Fix commit: a654a44 (fix(send): allow_busy opt-in…)
  • Ignore commit: b872377 (chore: ignore docs.local/) — unrelated housekeeping, happy to drop if noisy.

Formatter note

The src/server.ts diff includes a handful of prettier-style reflows (err signature one-liner, AgentEngine constructor call, a few .describe() wraps). These came along automatically from the global post-edit formatter; they're not intentional scope creep and don't change behavior. Happy to isolate into a prior chore: commit if the review prefers.

Test plan

  • bun run test — 444/448 pass. The 4 remaining failures are in tests/landing-polish.test.ts, which is untracked (not part of this branch) and pre-existing on main. Unrelated to this change.
  • bun run typecheck — clean.
  • New test send_to with allow_busy=true delivers to agents in working state passes.
  • New test send_to_agent with allow_busy=true delivers to agents in working state passes.
  • New test send_to without allow_busy still rejects working agents (backwards compat) passes.

🤖 Generated with Claude Code


Note

Medium Risk
Adds an opt-in path to bypass agent-state gating and send keystrokes to busy/working agents, which could change orchestrator behavior if misused; defaults remain unchanged and coverage is added via new tests.

Overview
Enables orchestrators to optionally interject input while an agent is busy by adding allow_busy (default false) to the send_to and send_to_agent tools.

When allow_busy: true, deliverAgentInput bypasses the interactive-state check and the rejection error message now points callers to this flag; new integration tests cover both the working-state delivery and backwards-compatible rejection behavior. Also ignores docs.local/ and includes a few formatter-only reflows.

Reviewed by Cursor Bugbot for commit b872377. Bugbot is set up for automated code reviews on this repo. Configure here.

Summary by CodeRabbit

Release Notes

  • New Features

    • Added allow_busy parameter to agent message delivery tools, enabling messages to be sent to agents in non-interactive/busy states when allow_busy: true is specified.
  • Tests

    • Added comprehensive integration tests validating agent message delivery behavior with the new allow_busy parameter.

Note

Add allow_busy opt-in to send_to and send_to_agent tools to bypass interactive state check

By default, send_to and send_to_agent reject delivery when an agent is not in an interactive state. This adds an optional allow_busy boolean parameter (default false) to both tools that skips this gate, allowing text to be delivered to agents in any state (e.g. working). The check is enforced in deliverAgentInput in server.ts, which now surfaces the allow_busy override in its error message when the gate fires.

Macroscope summarized b872377.

EtanHey and others added 3 commits April 23, 2026 14:15
Adds two failing tests + one backwards-compat guard:

1. send_to with allow_busy=true should deliver to agents in state=working
2. send_to_agent with allow_busy=true should deliver in state=working
3. send_to without allow_busy still rejects state=working (preserved contract)

Tests (1) and (2) fail: the deliverAgentInput gate rejects any state
outside {ready, idle} regardless of caller intent, and send_input works
on the same surface which is the inconsistency this PR addresses.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…to/send_to_agent

Adds `allow_busy: boolean` (default false) to `send_to` and `send_to_agent`.
When true, bypasses the `INTERACTIVE_AGENT_STATES` gate in `deliverAgentInput`
and delivers raw keystrokes regardless of state (matches `send_input` behavior).
Lets orchestrators interject while an agent is working — cancel, steer, or stack
an instruction — without falling back to `send_input`.

Default unchanged: omitting `allow_busy` (or passing false) preserves the gate.

Error message updated to hint at the opt-in when the gate fires.

Formatter note: prettier-style reflows elsewhere in the file (err signature,
AgentEngine constructor call, a few describe() wraps) came along automatically
from the global post-edit formatter — not intentional scope creep.

Failing-test commit: 53da1b8 (test: failing case for send_to allow_busy opt-in)
Tests: tests/server-agent-tools.test.ts (448 total, 444 pass; the 4 remaining
failures are in tests/landing-polish.test.ts which is untracked, pre-existing,
and unrelated to this change).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Local-only session-mining/planning notes live under docs.local/ by convention
across the golems ecosystem (see CLAUDE.md "File Storage Rules"). Ignore them
so they don't accidentally land in commits.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Apr 23, 2026 •

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This pull request adds an allow_busy boolean option to the agent message delivery system. When enabled, the send_to and send_to_agent MCP tools can deliver input to agents regardless of their interactive state, bypassing the existing state gate. The feature includes refactored formatting and corresponding test coverage.

Changes

Cohort / File(s) Summary
Git Configuration
.gitignore
Adds docs.local/ directory to ignored paths.
Agent Delivery Feature
src/server.ts
Adds allow_busy?: boolean option to internal deliverAgentInput function. When true, bypasses the interactive-state validation gate for send_to and send_to_agent MCP tool schemas. Updates error message to mention the bypass option. Includes minor formatting refactors.
Feature Tests
tests/server-agent-tools.test.ts
Introduces three new integration tests validating allow_busy behavior: tests confirm send_to and send_to_agent deliver to "working" agents when allow_busy: true, and that both tools error appropriately when allow_busy is omitted for non-interactive agents. Includes minor formatting fix in existing test.

Sequence Diagram(s)

sequenceDiagram
    participant Client as MCP Client
    participant Server as Agent Server
    participant Agent as Agent Instance

    alt allow_busy = true
        Client->>Server: send_to(text, allow_busy: true)
        Server->>Server: Check allow_busy flag
        Note over Server: Bypass interactive state validation
        Server->>Agent: deliverAgentInput(text)
        Agent->>Server: Input delivered
        Server->>Client: Success response
    else allow_busy = false or omitted
        Client->>Server: send_to(text)
        Server->>Server: Check interactive state
        alt Agent in INTERACTIVE_AGENT_STATES
            Server->>Agent: deliverAgentInput(text)
            Agent->>Server: Input delivered
            Server->>Client: Success response
        else Agent not interactive
            Server->>Client: Error: not in interactive state
        end
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 A busy agent now can receive a call,
With allow_busy: true, we bypass it all!
The state machine bends to the caller's will,
New tests ensure the logic stays quite still.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: adding an allow_busy opt-in parameter to send_to and send_to_agent methods.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/send-to-allow-busy

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/server.ts (1)

2014-2032: ⚠️ Potential issue | 🟡 Minor

Update the stale send_to_agent description.

Line 2017 still says this path sends only to agents in ready or idle, but allow_busy: true now intentionally supports non-interactive states.

📝 Proposed description update
-      "Deprecated for client integrations: use `send_to` instead. Internal/advanced path for sending text input to an agent in `ready` or `idle` state.",
+      "Deprecated for client integrations: use `send_to` instead. Internal/advanced path for sending text input to an agent. By default the agent must be in `ready` or `idle`; pass `allow_busy: true` to deliver raw keystrokes regardless of state.",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/server.ts` around lines 2014 - 2032, The server.tool registration for
"send_to_agent" has a stale description claiming it only sends to agents in
`ready` or `idle`; update that description string in the
server.tool("send_to_agent", ...) block to reflect that when the `allow_busy`
parameter is true the endpoint will also deliver input to agents in
non-interactive/busy states (i.e., bypasses the interactive-state gate), and
mark the overall notice as deprecated for client integrations while keeping the
guidance to use `send_to` for normal cases; modify the human-readable
description next to server.tool("send_to_agent", ...) so it mentions
`allow_busy: true` supports non-interactive states.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tests/server-agent-tools.test.ts`:
- Around line 384-412: Update the test's assertion for the rejection from
send_to.handler so it also checks the new guidance that tells callers how to
bypass the busy-gate by setting allow_busy; locate the send_to handler test
block (the variables spawn.handler and send_to.handler) and change the
expect(result.content[0].text).toMatch(...) to include a regex that matches both
the original "not in an interactive state" message and the new instructions
referencing "allow_busy" (e.g. /not in an interactive state.*allow_busy/ or
similar), ensuring the rejection path asserts the updated guidance text.
- Line 338: The new test "send_to with allow_busy=true delivers to agents in
working state" was added outside the mirrored test structure; either move this
test into the mirrored test file for the server source (the test that
corresponds to the server module) so tests follow the src↔tests mirroring rule,
or codify the exception by adding this test file to the project’s
test-exceptions registry/config (or documenting the exemption in the
contributing/testing docs) so the deviation is explicit; update the test
location or the exceptions list and run the test suite to confirm no mirror-rule
failures.
- Around line 415-454: The test only checks success flags but not that
send_to_agent actually forwarded the payload; after calling sendTo.handler add
assertions that the delivery path was invoked (e.g., inspect mockExec or the
delivery call used by the deprecated tool) and that the call includes the
agentId, the text "force deliver", and the allow_busy flag; locate the test's
use of sendTo.handler and mockExec and add an assertion like verifying
mockExec.mock.calls contains an entry whose payload includes agent_id ===
agentId, text === "force deliver", and allow_busy === true so the deprecated
tool is proven to forward the flag and payload.

---

Outside diff comments:
In `@src/server.ts`:
- Around line 2014-2032: The server.tool registration for "send_to_agent" has a
stale description claiming it only sends to agents in `ready` or `idle`; update
that description string in the server.tool("send_to_agent", ...) block to
reflect that when the `allow_busy` parameter is true the endpoint will also
deliver input to agents in non-interactive/busy states (i.e., bypasses the
interactive-state gate), and mark the overall notice as deprecated for client
integrations while keeping the guidance to use `send_to` for normal cases;
modify the human-readable description next to server.tool("send_to_agent", ...)
so it mentions `allow_busy: true` supports non-interactive states.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 61ed573f-9847-496d-97ca-1ad4b36d1f3a

📥 Commits

Reviewing files that changed from the base of the PR and between 543bd72 and b872377.

📒 Files selected for processing (3)
  • .gitignore
  • src/server.ts
  • tests/server-agent-tools.test.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Cursor Bugbot
  • GitHub Check: Macroscope - Correctness Check
  • GitHub Check: build-site
  • GitHub Check: test
🧰 Additional context used
📓 Path-based instructions (3)
**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.ts: Build TypeScript source with tsc and ensure Node 20+ compatibility
Use Zod for schema validation in TypeScript

Files:

  • tests/server-agent-tools.test.ts
  • src/server.ts
**/tests/**/*.test.ts

📄 CodeRabbit inference engine (CLAUDE.md)

**/tests/**/*.test.ts: Test files must mirror source structure: src/foo.ts -> tests/foo.test.ts
Use Vitest for testing with 310 tests across 17 test files
No integration tests requiring a running cmux instance — all tests must be mocked

Files:

  • tests/server-agent-tools.test.ts
**/server.ts

📄 CodeRabbit inference engine (CLAUDE.md)

**/server.ts: Use MCP SDK (@modelcontextprotocol/sdk) for tool registration and handlers, returning { content: TextContent[], structuredContent?, isError? } format
All MCP tool handlers must use ok(data) / err(error) helpers for consistent response formatting
Agent lifecycle tools should be registered conditionally, skipped when skipAgentLifecycle: true

Files:

  • src/server.ts
🧠 Learnings (19)
📓 Common learnings
Learnt from: EtanHey
Repo: EtanHey/cmuxlayer PR: 1
File: src/agent-engine.ts:174-178
Timestamp: 2026-03-15T10:42:08.557Z
Learning: In the cmuxlayer project (`src/agent-engine.ts`), the `CmuxClient` methods `send()` and `sendKey()` are backed by a cmux socket that processes commands in order. Awaiting them sequentially guarantees the prior command is fully delivered before the next is sent — no additional delay or confirmation is needed between consecutive `send()`/`sendKey()` calls.
📚 Learning: 2026-04-01T20:31:10.910Z
Learnt from: CR
Repo: EtanHey/cmuxlayer PR: 0
File: site/CLAUDE.md:0-0
Timestamp: 2026-04-01T20:31:10.910Z
Learning: Applies to site/**/*agent*.test.{ts,tsx} : Agents must have comprehensive unit tests covering success and failure paths

Applied to files:

  • tests/server-agent-tools.test.ts
📚 Learning: 2026-04-01T22:26:52.152Z
Learnt from: CR
Repo: EtanHey/cmuxlayer PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-01T22:26:52.152Z
Learning: Applies to **/tests/agent-engine.test.ts : Agent engine tests must use 1-second timeouts for state change detection

Applied to files:

  • tests/server-agent-tools.test.ts
  • src/server.ts
📚 Learning: 2026-04-01T22:26:52.152Z
Learnt from: CR
Repo: EtanHey/cmuxlayer PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-01T22:26:52.152Z
Learning: Applies to **/tests/**/*.test.ts : No integration tests requiring a running cmux instance — all tests must be mocked

Applied to files:

  • tests/server-agent-tools.test.ts
📚 Learning: 2026-04-01T20:31:10.910Z
Learnt from: CR
Repo: EtanHey/cmuxlayer PR: 0
File: site/CLAUDE.md:0-0
Timestamp: 2026-04-01T20:31:10.910Z
Learning: Applies to site/**/*agent*.{ts,tsx} : Document agent purpose and usage in agent implementation files

Applied to files:

  • tests/server-agent-tools.test.ts
  • src/server.ts
📚 Learning: 2026-04-01T22:26:52.152Z
Learnt from: CR
Repo: EtanHey/cmuxlayer PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-01T22:26:52.152Z
Learning: Applies to **/server.ts : Agent lifecycle tools should be registered conditionally, skipped when `skipAgentLifecycle: true`

Applied to files:

  • tests/server-agent-tools.test.ts
  • src/server.ts
📚 Learning: 2026-04-01T22:26:52.152Z
Learnt from: CR
Repo: EtanHey/cmuxlayer PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-01T22:26:52.152Z
Learning: Applies to **/tests/server.test.ts : Server tests must mock the cmux client via `createServer({ exec, skipAgentLifecycle })` pattern

Applied to files:

  • tests/server-agent-tools.test.ts
  • src/server.ts
📚 Learning: 2026-04-01T22:26:52.152Z
Learnt from: CR
Repo: EtanHey/cmuxlayer PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-01T22:26:52.152Z
Learning: Applies to **/agent-registry.ts : Implement agent registry to track active agents across surfaces

Applied to files:

  • tests/server-agent-tools.test.ts
  • src/server.ts
📚 Learning: 2026-03-15T10:46:40.958Z
Learnt from: EtanHey
Repo: EtanHey/cmuxlayer PR: 1
File: tests/sidebar-sync.test.ts:18-77
Timestamp: 2026-03-15T10:46:40.958Z
Learning: In the cmuxlayer project, each test file (e.g., tests/sidebar-sync.test.ts, tests/quality-tracking.test.ts, tests/agent-hierarchy.test.ts) is intentionally self-contained. All mock setup helpers (makeMockClient, makeSurface, makeRecord) are defined locally within each test file rather than in shared fixtures. This is a deliberate design choice so that when a test fails, all context is in one file. Shared fixtures are avoided to prevent coupling between test suites. Minor drift in mock fields across files (e.g., listStatus present in one file but not another) is acceptable — it only matters when a test explicitly calls that method. Do not flag duplicated test helpers or suggest extracting them into shared fixture modules.

Applied to files:

  • tests/server-agent-tools.test.ts
📚 Learning: 2026-04-01T20:31:10.910Z
Learnt from: CR
Repo: EtanHey/cmuxlayer PR: 0
File: site/CLAUDE.md:0-0
Timestamp: 2026-04-01T20:31:10.910Z
Learning: Applies to site/**/*agent*.{ts,tsx} : Use the Agent interface/base class for creating new agents

Applied to files:

  • tests/server-agent-tools.test.ts
  • src/server.ts
📚 Learning: 2026-04-01T20:31:10.910Z
Learnt from: CR
Repo: EtanHey/cmuxlayer PR: 0
File: site/CLAUDE.md:0-0
Timestamp: 2026-04-01T20:31:10.910Z
Learning: Applies to site/**/*agent*.{ts,tsx} : Use logging for agent actions and state transitions

Applied to files:

  • tests/server-agent-tools.test.ts
  • src/server.ts
📚 Learning: 2026-03-15T10:42:08.557Z
Learnt from: EtanHey
Repo: EtanHey/cmuxlayer PR: 1
File: src/agent-engine.ts:174-178
Timestamp: 2026-03-15T10:42:08.557Z
Learning: In the cmuxlayer project (`src/agent-engine.ts`), the `CmuxClient` methods `send()` and `sendKey()` are backed by a cmux socket that processes commands in order. Awaiting them sequentially guarantees the prior command is fully delivered before the next is sent — no additional delay or confirmation is needed between consecutive `send()`/`sendKey()` calls.

Applied to files:

  • tests/server-agent-tools.test.ts
  • src/server.ts
📚 Learning: 2026-03-16T22:37:27.455Z
Learnt from: EtanHey
Repo: EtanHey/cmuxlayer PR: 0
File: :0-0
Timestamp: 2026-03-16T22:37:27.455Z
Learning: In the cmuxlayer project (src/agent-engine.ts / src/agent-types.ts), the inconsistency between `buildLaunchCommand` (throws on `/` in repo names for shell arg safety) and `generateAgentId` (sanitizes `/` to `-` for key safety) is intentional and tracked for follow-up. Do not flag this mismatch as a bug. Both approaches are valid for their respective contexts.

Applied to files:

  • tests/server-agent-tools.test.ts
  • src/server.ts
📚 Learning: 2026-03-15T10:42:35.917Z
Learnt from: EtanHey
Repo: EtanHey/cmuxlayer PR: 1
File: tests/quality-tracking.test.ts:171-200
Timestamp: 2026-03-15T10:42:35.917Z
Learning: In tests/quality-tracking.test.ts for the cmuxlayer project, ensure that at or above 80% context quality degradation, behavior depends on depth: depth-0 agents receive a /compact command; depth > 0 agents are killed and logged (kill + log). Respawn of non-root agents is out of scope for v1. Treat the design doc quality tracking section as the authoritative source for this behavior, and align test expectations accordingly.

Applied to files:

  • tests/server-agent-tools.test.ts
📚 Learning: 2026-04-01T20:31:10.910Z
Learnt from: CR
Repo: EtanHey/cmuxlayer PR: 0
File: site/CLAUDE.md:0-0
Timestamp: 2026-04-01T20:31:10.910Z
Learning: Applies to site/**/*agent*.{ts,tsx} : Use type definitions for agent inputs, outputs, and configuration

Applied to files:

  • src/server.ts
📚 Learning: 2026-04-01T22:26:52.152Z
Learnt from: CR
Repo: EtanHey/cmuxlayer PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-01T22:26:52.152Z
Learning: Applies to **/server.ts : All MCP tool handlers must use `ok(data)` / `err(error)` helpers for consistent response formatting

Applied to files:

  • src/server.ts
📚 Learning: 2026-04-01T22:26:52.152Z
Learnt from: CR
Repo: EtanHey/cmuxlayer PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-01T22:26:52.152Z
Learning: Applies to **/server.ts : Use MCP SDK (`modelcontextprotocol/sdk`) for tool registration and handlers, returning `{ content: TextContent[], structuredContent?, isError? }` format

Applied to files:

  • src/server.ts
📚 Learning: 2026-04-01T16:08:15.301Z
Learnt from: EtanHey
Repo: EtanHey/cmuxlayer PR: 0
File: :0-0
Timestamp: 2026-04-01T16:08:15.301Z
Learning: In the cmuxlayer project (src/agent-engine.ts), `buildLaunchCommand` intentionally does NOT use Zod for input validation. The function is internal (called only from `spawnAgent`), and upstream Zod schema validation already occurs in server.ts around lines 884-886. Adding Zod at this layer is considered redundant. The regex + explicit `.`/`..` path-traversal rejection is the sufficient sanitization boundary.

Applied to files:

  • src/server.ts
📚 Learning: 2026-03-16T22:37:27.796Z
Learnt from: EtanHey
Repo: EtanHey/cmuxlayer PR: 0
File: :0-0
Timestamp: 2026-03-16T22:37:27.796Z
Learning: In the cmuxlayer project (src/agent-registry.ts), orphan reparenting is NOT part of V1. When a parent agent crashes, children intentionally keep their parent_agent_id pointing to the dead parent (orphan survival). Reparenting children to root (setting parent_agent_id to null) is a V2 design feature that will be introduced in a dedicated future PR with its own tests. Do not flag missing reparenting logic in agent-registry.ts until the V2 reparenting PR lands.

Applied to files:

  • src/server.ts
🔇 Additional comments (3)
.gitignore (1)

4-4: LGTM.

Ignoring docs.local/ is scoped to local/generated documentation artifacts and does not affect the runtime changes.

src/server.ts (2)

1664-1694: LGTM: the bypass is scoped to the state gate only.

allow_busy preserves the default interactive-state guard while keeping sanitization, chunking, and withSurfaceWrite protections on the delivery path.


1977-2005: LGTM: send_to exposes the opt-in safely.

The Zod schema defaults allow_busy to false, and the handler forwards it explicitly into deliverAgentInput.

expect(result.content[0].text).toMatch(/not in an interactive state/);
});

it("send_to with allow_busy=true delivers to agents in working state", async () => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Place new server coverage in the mirrored server test file, or document the exception.

These new src/server.ts tests are being added to tests/server-agent-tools.test.ts, which does not follow the source/test mirroring rule. If this suite is intentionally exempt, please codify that exception. As per coding guidelines, "**/tests/**/*.test.ts: Test files must mirror source structure: src/foo.ts -> tests/foo.test.ts."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/server-agent-tools.test.ts` at line 338, The new test "send_to with
allow_busy=true delivers to agents in working state" was added outside the
mirrored test structure; either move this test into the mirrored test file for
the server source (the test that corresponds to the server module) so tests
follow the src↔tests mirroring rule, or codify the exception by adding this test
file to the project’s test-exceptions registry/config (or documenting the
exemption in the contributing/testing docs) so the deviation is explicit; update
the test location or the exceptions list and run the test suite to confirm no
mirror-rule failures.

Comment on lines +384 to +412
it("send_to without allow_busy still rejects working agents (backwards compat)", async () => {
const server = createLifecycleServer(mockExec);
const spawn = (server as any)._registeredTools["spawn_agent"];
const sendTo = (server as any)._registeredTools["send_to"];

const spawnResult = await spawn.handler(
{
repo: "brainlayer",
model: "sonnet",
cli: "claude",
prompt: "test",
},
{} as any,
);
const agentId = (
spawnResult.structuredContent ?? JSON.parse(spawnResult.content[0].text)
).agent_id;

const engine = (server as any)._registeredTools["interact"]._engine;
const registry = engine.getRegistry();
const agent = registry.get(agentId);
registry.set(agentId, { ...agent, state: "working" });

const result = await sendTo.handler(
{ agent_id: agentId, text: "hello", press_enter: true },
{} as any,
);
expect(result.isError).toBe(true);
expect(result.content[0].text).toMatch(/not in an interactive state/);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Assert the new allow_busy guidance in the rejection path.

This test protects backwards compatibility, but it does not lock the PR’s updated error guidance that tells callers how to bypass the gate.

🧪 Proposed assertion
     expect(result.isError).toBe(true);
     expect(result.content[0].text).toMatch(/not in an interactive state/);
+    expect(result.content[0].text).toMatch(/allow_busy: true/);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/server-agent-tools.test.ts` around lines 384 - 412, Update the test's
assertion for the rejection from send_to.handler so it also checks the new
guidance that tells callers how to bypass the busy-gate by setting allow_busy;
locate the send_to handler test block (the variables spawn.handler and
send_to.handler) and change the expect(result.content[0].text).toMatch(...) to
include a regex that matches both the original "not in an interactive state"
message and the new instructions referencing "allow_busy" (e.g. /not in an
interactive state.*allow_busy/ or similar), ensuring the rejection path asserts
the updated guidance text.

Comment on lines +415 to +454
it("send_to_agent with allow_busy=true delivers to agents in working state", async () => {
const server = createLifecycleServer(mockExec);
const spawn = (server as any)._registeredTools["spawn_agent"];
const sendTo = (server as any)._registeredTools["send_to_agent"];

const spawnResult = await spawn.handler(
{
repo: "brainlayer",
model: "sonnet",
cli: "claude",
prompt: "test",
},
{} as any,
);
const agentId = (
spawnResult.structuredContent ?? JSON.parse(spawnResult.content[0].text)
).agent_id;

const engine = (server as any)._registeredTools["interact"]._engine;
const registry = engine.getRegistry();
const agent = registry.get(agentId);
registry.set(agentId, { ...agent, state: "working" });
mockExec.mockClear();

const result = await sendTo.handler(
{
agent_id: agentId,
text: "force deliver",
press_enter: true,
allow_busy: true,
},
{} as any,
);
const parsed =
result.structuredContent ?? JSON.parse(result.content[0].text);

expect(result.isError).toBeFalsy();
expect(parsed.ok).toBe(true);
expect(parsed.agent_id).toBe(agentId);
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Verify send_to_agent actually delivers the text.

The test currently proves the response is successful, but not that the deprecated tool forwards allow_busy into the delivery path and sends the intended payload.

🧪 Proposed delivery assertion
     const parsed =
       result.structuredContent ?? JSON.parse(result.content[0].text);
+    const sendCalls = mockExec.mock.calls.filter(
+      ([, args]) => Array.isArray(args) && args.includes("send"),
+    );
+    const deliveredText = sendCalls.map(([, args]) => args.at(-1)).join("");
 
     expect(result.isError).toBeFalsy();
     expect(parsed.ok).toBe(true);
     expect(parsed.agent_id).toBe(agentId);
+    expect(deliveredText).toBe("force deliver");
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/server-agent-tools.test.ts` around lines 415 - 454, The test only
checks success flags but not that send_to_agent actually forwarded the payload;
after calling sendTo.handler add assertions that the delivery path was invoked
(e.g., inspect mockExec or the delivery call used by the deprecated tool) and
that the call includes the agentId, the text "force deliver", and the allow_busy
flag; locate the test's use of sendTo.handler and mockExec and add an assertion
like verifying mockExec.mock.calls contains an entry whose payload includes
agent_id === agentId, text === "force deliver", and allow_busy === true so the
deprecated tool is proven to forward the flag and payload.

@EtanHey
EtanHey merged commit 751a71a into main Apr 23, 2026
5 checks passed
@EtanHey
EtanHey deleted the fix/send-to-allow-busy branch April 23, 2026 12:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant