Repository navigation
fix(send): allow_busy opt-in for send_to / send_to_agent - #86
Conversation
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>
📝 WalkthroughWalkthroughThis pull request adds an Changes
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
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
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 | 🟡 MinorUpdate the stale
send_to_agentdescription.Line 2017 still says this path sends only to agents in
readyoridle, butallow_busy: truenow 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
📒 Files selected for processing (3)
.gitignoresrc/server.tstests/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 withtscand ensure Node 20+ compatibility
Use Zod for schema validation in TypeScript
Files:
tests/server-agent-tools.test.tssrc/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 useok(data)/err(error)helpers for consistent response formatting
Agent lifecycle tools should be registered conditionally, skipped whenskipAgentLifecycle: 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.tssrc/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.tssrc/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.tssrc/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.tssrc/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.tssrc/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.tssrc/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.tssrc/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.tssrc/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.tssrc/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_busypreserves the default interactive-state guard while keeping sanitization, chunking, andwithSurfaceWriteprotections on the delivery path.
1977-2005: LGTM:send_toexposes the opt-in safely.The Zod schema defaults
allow_busytofalse, and the handler forwards it explicitly intodeliverAgentInput.
| 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 () => { |
There was a problem hiding this comment.
🧹 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.
| 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/); |
There was a problem hiding this comment.
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.
| 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); | ||
| }); |
There was a problem hiding this comment.
🧹 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.
Summary
Adds
allow_busy: booleanopt-in tosend_toandsend_to_agentMCP endpoints. Whentrue, bypasses thenot in an interactive stategate indeliverAgentInputand delivers raw keystrokes regardless of agent state (matchessend_inputbehavior). Default unchanged: omittingallow_busy(or passingfalse) 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_agentto an agent instate=working, the call rejects withnot in an interactive state. There's no clean way to stack an instruction without waiting for idle or falling back tosend_input(which has its own quirks). Inconsistent withsend_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:
send_inputsemantics — consistent mental model across the two entrypoints.The error message now points callers at the opt-in so the next time the gate fires, the remediation is self-describing.
TDD evidence
53da1b8(test: failing case for send_to allow_busy opt-in)a654a44(fix(send): allow_busy opt-in…)b872377(chore: ignore docs.local/) — unrelated housekeeping, happy to drop if noisy.Formatter note
The
src/server.tsdiff includes a handful of prettier-style reflows (errsignature one-liner,AgentEngineconstructor 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 priorchore:commit if the review prefers.Test plan
bun run test— 444/448 pass. The 4 remaining failures are intests/landing-polish.test.ts, which is untracked (not part of this branch) and pre-existing onmain. Unrelated to this change.bun run typecheck— clean.send_to with allow_busy=true delivers to agents in working statepasses.send_to_agent with allow_busy=true delivers to agents in working statepasses.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(defaultfalse) to thesend_toandsend_to_agenttools.When
allow_busy: true,deliverAgentInputbypasses 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 ignoresdocs.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
allow_busyparameter to agent message delivery tools, enabling messages to be sent to agents in non-interactive/busy states whenallow_busy: trueis specified.Tests
allow_busyparameter.Note
Add
allow_busyopt-in tosend_toandsend_to_agenttools to bypass interactive state checkBy default,
send_toandsend_to_agentreject delivery when an agent is not in an interactive state. This adds an optionalallow_busyboolean parameter (defaultfalse) to both tools that skips this gate, allowing text to be delivered to agents in any state (e.g.working). The check is enforced indeliverAgentInputin server.ts, which now surfaces theallow_busyoverride in its error message when the gate fires.Macroscope summarized b872377.