Skip to main content
Glama

reasonix-mcp — spawn and steer Reasonix agents from any MCP client

An MCP server that bridges any MCP host (Claude Code, Codex, …) to live, interactive Reasonix agents. Your MCP client is the front-end; this project adds the server it talks to, plus a detached daemon that owns the agents. Each spawned agent runs reasonix acp (Agent Client Protocol v1, NDJSON JSON-RPC over stdio) in a subprocess, rooted in your project, under your own Reasonix config and provider credentials.

How it works

MCP host (Claude Code, Codex, …) ──(MCP stdio)──▶ launcher.py ──▶ server.py ──(Unix socket JSON-RPC)──▶ agentd ──(ACP stdio)──▶ reasonix acp ──▶ agents
                                   │  spawn/send/watch/poll/list/…       ▲ owns the subprocesses
                                   └─ blocking watch + elicitation ──────┘

launcher.py is the per-orchestrator MCP supervisor; server.py is a thin MCP front-end; agentd.py is a detached daemon that owns the agents. This split is what makes agents survive: close the MCP host (or kill the MCP server) and the fleet keeps running in the daemon — a new server reconnects to the same socket and reasonix_list shows everything still there. agentd is auto-started by the server on first use (socket: ~/.reasonix-mcp/agentd.sock, log beside it) and stops only when shut down (killing the daemon kills its agents — they carry PDEATHSIG).

  • Spawn returns a session_id immediately; the agent works in the daemon.

  • Send steers a running agent mid-turn via _reasonix.io/session/steer; if idle it starts a new turn.

  • Poll returns a compact status, capped message, plan/current work, permission request, stop reason, and terminal error text when applicable. Diagnostic fields are opt-in with detail=true.

  • Cleanup can stop completed agents after an idle grace period; it is disabled by default. Use keep_alive=true for interactive follow-up turns.

  • Resume revives a stopped/crashed session from its persisted transcript.

  • Configure switches model/effort/session options immediately when idle, after the current turn when active, or on the next resume when stopped.

  • Stop cancels + closes + kills; the session stays listed and resumable.

  • Wake-up: keep reasonix_watch in flight. It returns compact terminal or permission results directly, with no timeout and no follow-up poll by default. Wire notifications are diagnostics only.

Related MCP server: agentforge

Setup

cd ~/reasonix-mcp
python3 -m venv .venv
.venv/bin/pip install -r requirements.txt

Requires reasonix on PATH (verified: v1.17.20) and a configured provider (reasonix setup).

Register with your MCP client

Project scope (this directory): the included .mcp.json is picked up automatically when the MCP host runs in ~/reasonix-mcp.

User scope (available in every project) — example with Claude Code:

claude mcp add reasonix --scope user -- \
  /home/asmodeus/reasonix-mcp/.venv/bin/python \
  /home/asmodeus/reasonix-mcp/src/reasonix_mcp/launcher.py

For Codex, register the same launcher and raise the per-tool transport timeout because reasonix_watch is intentionally long-lived:

[mcp_servers.reasonix]
command = "/home/asmodeus/reasonix-mcp/.venv/bin/python"
args = ["/home/asmodeus/reasonix-mcp/src/reasonix_mcp/launcher.py"]
tool_timeout_sec = 86400

Codex defaults to a finite per-tool timeout (and existing installations may use a shorter explicit value such as 300 seconds). A timed out awaiting tools/call error at that exact interval is the Codex transport ending the call, not Reasonix timing out or an agent failing. Reload Codex after changing config.toml; restarting only the Reasonix MCP server does not reload the host's timeout setting. Never restart MCP or agentd for a host-side tool timeout—reissue one watch for the complete remaining fleet.

Other MCP hosts register the stdio server through their own mcp add equivalents—same command and arguments, different client. Then restart the MCP client and verify the server is listed.

Tools

Tool

Purpose

reasonix_spawn(task, cwd?, model?, work_mode?, tool_approval?, effort?, keep_alive?, idle_timeout?)

Start an agent in the daemon on task; returns session_id + sandbox posture. Completed agents can be cleaned up after the idle grace period when cleanup is enabled, unless keep_alive=true.

reasonix_spawn_fleet(path)

Validate an assigned-fleet JSON file and spawn all agents in one call. Returns ordered agent/session mappings and session_ids ready for one reasonix_watch call.

reasonix_resume(session_id, cwd?, model?, effort?, work_mode?, tool_approval?, keep_alive?, idle_timeout?)

Revive a stopped/crashed session from its persisted transcript, optionally changing its model/options. Idempotent for live processes.

reasonix_resume_fleet(session_ids, model?, effort?, work_mode?, tool_approval?, keep_alive?, idle_timeout?, parallelism?, continue_interrupted?)

Bulk-revive stopped/crashed/orphaned sessions with bounded startup concurrency and per-session results. Interrupted turns continue by default; previously idle/stopped sessions stay idle.

reasonix_models()

List selectable models: provider/model refs, default, per-model supported_efforts, and price hints where configured.

reasonix_send(session_id, message, expect?)

Forced steer: queue as mid-turn guidance, or start a new turn if idle. Never dropped. expect="steer" refuses to start a new turn.

reasonix_broadcast(session_ids, message, expect?, parallelism?)

Send one instruction to selected agents concurrently. Returns compact per-agent delivery/failure results; successful sends are not rolled back if another agent fails.

reasonix_configure(session_id, model?, effort?, work_mode?, tool_approval?)

Switch session configuration while preserving history. Idle: immediate. Active: queued before next turn. Exited: persisted for reasonix_resume.

reasonix_poll(session_id, detail?, include_events?, exclude_events?, include_thought?, include_full?, max_events?)

Context-lean status / capped message / current plan-work / pending permission. Set detail=true for diagnostic events, turns, counters, transcript path, and configuration.

reasonix_transcript(session_id, max_tool_calls?)

What the agent actually did: tool calls with args, files touched (write/read/bash), roles, work duration, last text — powers rebase decisions. (Reasonix does not persist token/cost usage; these are activity metrics.)

reasonix_watch(session_ids, timeout?, detail?, …poll options)

Primary callback: block indefinitely by default until completion, permission/question, or process death. Returns a compact result; detail=true opts into the poll-shaped result.

reasonix_wait(session_ids, timeout?)

Legacy event-driven wait until any watched session produces output, finishes a turn, or raises a permission request.

reasonix_list(include_task?, pending_only?, detail?)

Compact fleet inventory with aggregate status counts. pending_only=true returns only active, decision-blocked, or uncollected terminal sessions. detail=true opts into fleet-wide plans/config/transcript paths; prefer poll for one agent.

reasonix_respond_permission(session_id, option_id)

Answer a tool-approval request (option_id from watch/poll's permission_request.options, or "cancel").

reasonix_stop(session_id)

Cancel + close + kill the agent (tombstone: poll keeps reporting exited).

reasonix_restart_agentd(force?)

Verified daemon replacement. Source changes queue a safe automatic reload; force=true checkpoints active IDs, then escalates cooperative shutdown through TERM/KILL if needed and returns only after a distinct current-code daemon answers ping.

reasonix_restart_mcp_server()

Explicitly restart this orchestrator's MCP server through launcher.py; source changes are watched automatically.

reasonix_dashboard(open_browser?)

Open or reuse the authenticated local fleet UI for current and previous agents across orchestrators. Supports live steering, stop/resume, configuration, and permission responses.

reasonix_routine_create(name, prompt, …)

Create a durable manual, interval, or daily loop agent. Each iteration starts in fresh context and may delegate through native Reasonix subagents.

reasonix_routine_templates()

List curated maintenance templates with complete prompts and safe defaults.

reasonix_routine_from_template(template_id, cwd, …overrides)

Create an editable routine from a curated template.

reasonix_routine_list(include_prompt?, include_results?)

Compact routine state, schedule, queued triggers, and latest 20 runs for this orchestrator. Full prompts/results are opt-in.

reasonix_routine_configure(routine_id, …)

Edit, pause, or enable a routine; changes apply to future runs.

reasonix_routine_run(routine_id)

Queue an immediate run while honoring its skip/queue overlap policy.

reasonix_routine_stop(routine_id)

Stop the current parent agent without disabling future scheduled runs.

reasonix_routine_delete(routine_id)

Delete an inactive routine and its scheduler run history. Agent transcripts remain in Reasonix.

Assigned fleets

Use reasonix_spawn_fleet(path) when an orchestrator has already divided work among several agents. The complete file is validated before any process starts. Shared defaults use the same options as reasonix_spawn; each agent can override them:

{
  "name": "oracle audit",
  "parallelism": 4,
  "defaults": {
    "cwd": "/home/user/project",
    "model": "codex/gpt-5.6-sol",
    "effort": "high",
    "tool_approval": "yolo"
  },
  "agents": [
    {"name": "compile", "task": "Repair and verify compile failures."},
    {"name": "traps", "task": "Classify and repair trap mismatches."},
    {
      "name": "harness",
      "task": "Investigate harness setup failures.",
      "model": "opencode-go/deepseek-v4-flash",
      "effort": "max",
      "tool_approval": "ask"
    }
  ]
}

agents may instead be an object mapping names to task strings or assignment objects. tasks is accepted as an alias, and a root array is accepted for small files. Relative cwd values resolve relative to the JSON file. Fleet size is capped at 64 so all returned IDs fit one reasonix_watch; startup parallelism defaults to 8 and can be set from 1 through 16. Predictable schema, path, and configuration errors reject the whole file before spawning. A provider/process failure during startup is returned on that agent while the other successful session IDs remain usable.

Reasonix holds a safety lease per real workspace while a turn is active. Fleet members with the same cwd therefore queue rather than execute tools in parallel. Give each writing agent its own worktree and cwd for true concurrency. reasonix_spawn_fleet reports shared_workspaces when its JSON would create this contention; its parallelism setting controls concurrent spawn handshakes, not workspace tool execution.

Agent health distinguishes three active states. running means the ACP pipe or a live tool subprocess has advanced recently; blocked means the turn is queued behind the named workspace_lease_holder; stalled means the ACP process is alive but neither its protocol nor process tree has advanced for 10 minutes. The heartbeat reports these separately, reasonix_list and anomalous poll/watch results include compact health evidence, and the dashboard updates the status automatically. A stalled transition wakes reasonix_watch once; ordinary workspace queueing does not. This is diagnostic only—Reasonix MCP does not kill a quiet turn automatically. Override the diagnostic threshold with REASONIX_MCP_RUNTIME_STALLED_AFTER when a provider legitimately needs longer.

Each ACP child has one stdout and one stderr reader thread. Those threads normally sleep in anon_pipe_read while the child is quiet; their count alone is not evidence of a daemon wedge. The liveness decision uses protocol and process-tree progress instead of thread count or PID existence.

If native Reasonix ends a turn with background bash failed: needs attention, agentd treats that exact error as recoverable and starts a fresh turn on the same session. The continuation is told to inspect the failed job once, stop polling its terminal job id, correct the command, and continue the remaining task. This is capped at three consecutive attempts; after that, or if the new turn cannot start, normal terminal watch/error delivery resumes. Override the cap with REASONIX_MCP_BACKGROUND_RECOVERY_ATTEMPTS (set it to 0 to disable automatic recovery). Other errors are never retried automatically.

Local fleet dashboard

The dashboard starts automatically with the first Reasonix MCP server and is reused by additional orchestrators. Call reasonix_dashboard() to ensure it is current and open it in a browser. The dark three-pane fleet UI is fixed at http://127.0.0.1:8746; the tool returns that URL when the environment has no desktop browser. Set REASONIX_MCP_DASHBOARD_AUTOSTART=0 only when automatic startup is not wanted. Log in with the default password CHANGEME. Set REASONIX_MCP_DASHBOARD_PASSWORD before launching the MCP client to choose a different password. Override the fixed port with REASONIX_MCP_DASHBOARD_PORT when 8746 is reserved by another local service. Credentials are never included in the URL. The browser retains an accepted password only in tab-scoped session storage; runtime state is stored mode 0600 in ~/.reasonix-mcp/dashboard.json. The service refuses non-loopback binds and authenticates every API, event stream, and action.

The sidebar groups current and previous sessions by orchestrator. Selecting an agent shows its persisted runs, agent messages, reasoning blocks, tool calls and results, plan, and current work. Orchestrators are collapsible preview buttons; active runs are the default view and fleets/runs are ordered newest first. The selected agent or loop-agent ID is kept in the credential-free URL fragment, so refreshing the page restores the same detail view—including a routine-owned run that is not listed in an ordinary orchestrator fleet. The selected run timeline opens at its latest activity. Accepted mid-turn steering messages are persisted in an owner-scoped orchestration timeline and merged at their transcript byte boundary, so the guidance appears between the agent events that happened before and after it even when Reasonix does not write the steer into its own JSONL. For a live agent, the user can steer or follow up, stop it, switch model, effort, work mode, or approval policy, and answer pending permission or agent questions. The control panel shows the values reported by the agent separately from queued changes. For sessions created before configuration sidecars existed, the dashboard recovers the actual model, effort, mode, and approval from Reasonix's persisted .acp.json session metadata and backfills the sidecar. Missing values remain Unknown; defaults are never guessed. New spawns persist their effective settings at both the MCP and daemon boundaries, so they remain verified across upgrades. Stopped and historical sessions can be resumed from the UI.

Dashboard observation is deliberately separate from orchestration delivery: it opens one read-only event socket per owner and never calls reasonix_watch, reasonix_wait, or reasonix_poll to refresh state. Watching the UI therefore does not supersede a CLI watch, consume terminal output, or add transcript data to an orchestrator's context. Session ownership is resolved from server-side owner sidecars; the browser cannot nominate an owner for an action. Multiple CLI orchestrators can keep running while one dashboard observes all of their local fleets.

Only one healthy dashboard is reused. Calling reasonix_dashboard() after its HTML, CSS, JavaScript, or backend source changes replaces that verified dashboard process and starts the current code; agentd and every agent remain untouched. The replacement preserves port 8746 and its configured password, so already-open browser tabs reconnect to the upgraded dashboard.

Loop agents and routines

Loop agents are durable maintenance routines. They run independently of the MCP stdio process, IDE, CLI orchestrator, and dashboard. A singleton routined.py scheduler stores definitions and the latest 100 run records as mode-0600 JSON under ~/.reasonix-mcp/routines/. It reloads its own source in place when code changes; running agents remain in agentd and are not interrupted. The default global limit is four concurrent routine parents; override it with REASONIX_MCP_ROUTINE_CONCURRENCY.

Every iteration calls spawn and receives a new session and context. The routine instructions and execution contract are injected exactly once into that new session: inspect current state, act idempotently, and avoid duplicate issues, branches, PRs, or fixes. Previous context is never resumed. Durable run history keeps the final result and a link to the persisted agent transcript, but it is not fed into the next prompt. A crash after spawn is recovered using the unique run marker rather than spawning duplicate work.

Schedules can be manual, fixed-delay interval, or daily at HH:MM in an IANA timezone. If the machine was off, a past-due schedule runs once after the scheduler returns; missed intervals are not replayed in a burst. With overlap_policy=skip, a due tick while the prior run is active is recorded as skipped. With queue, up to ten triggers are retained and executed serially. Pausing prevents scheduled runs but still permits an explicit Run now.

delegation=allowed or encouraged tells the fresh parent it may use Reasonix's native task, fleet, parallel_tasks, or configured subagent skills. Child lifecycle remains managed by Reasonix, and the parent must review child results before acting. disabled explicitly prohibits delegation for that routine.

The dashboard's Loop agents section can create and fully configure a routine, run it immediately, pause/enable it, stop its active parent, inspect run results, or open the run's normal agent timeline. Routine sessions use a separate internal owner, so they do not pollute ordinary orchestrator fleets. The creator's MCP client can only list or mutate its own routines; the local authenticated dashboard intentionally provides machine-wide administration.

The creation form also includes curated starting templates for GitHub issue maintenance, PR review, crash fuzzing, duplicate unification, dead-code and weak-test removal, flaky tests, logic simplification and bug finding, shipped feature cleanup, forgotten internal features, and abstraction review. A template copies a complete prompt and conservative defaults into the form; the user can inspect and edit every field before saving. Templates are equally available to orchestrators through reasonix_routine_templates() and reasonix_routine_from_template().

Unattended routines default to tool_approval=auto, not YOLO. Choose YOLO only for trusted repositories and tightly scoped prompts. Ask-mode permission and agent-question gates remain pending until answered from the dashboard; the scheduler never guesses. External effects such as creating or merging a GitHub PR require credentials and explicit authorization in the routine prompt. A routine does not auto-merge merely because it can inspect GitHub.

Example daily issue/PR maintenance configuration:

name: GitHub issue maintainer
schedule: daily, 09:00 Europe/Athens
overlap: skip
delegation: encouraged
approval: auto
prompt: Inspect currently open issues and PRs. Do not duplicate existing work.
        Select at most one well-scoped mechanical fix. Delegate independent
        investigation when useful, review the result, run focused tests, and
        open a PR with evidence. Never merge or close user reports yourself.

Completion and decision delivery

Keep reasonix_watch(session_ids) in flight while agents work. It returns when a child finishes, exits, or needs a decision, with each compact result in results[session_id]. By default each result contains only status, stop reason, one capped message, permission details, plan/current work, errors, and transcript path. Its timeout is disabled by default and it does not wake for ordinary text chunks, so no timer loop or follow-up poll is needed. Omit timeout for normal orchestration. Values such as timeout=240 in the self-tests are test-runner safety guards, not production defaults. Use one watch per orchestrator fleet. If another child finishes while a result is being handled, its terminal state remains pending and the next watch returns it immediately. A newer overlapping watch supersedes the older call safely; a stale canceled watch never requires an MCP-server restart. The displaced call returns superseded=true, and the newest call owns the fleet. Always include the complete remaining fleet when replacing a watch. If the MCP host or server restarts during a watch, reconnect, recover the owned fleet with reasonix_list(pending_only=true), and watch it again; undelivered terminal state remains pending in agentd.

If agentd itself dies or is force-restarted, active lifecycle checkpoints are rediscovered as status: "orphaned" with process_alive: false. Their cwd, task, ownership, and configuration remain on disk. Pass the returned ids to reasonix_resume_fleet; each session is reopened from its persisted transcript and failures are isolated per session.

reasonix_restart_agentd returns recoverable_sessions separately from checkpointed_sessions. The former is the durable active/interrupted set to resume; completed idle history is deliberately excluded. restarted: true also guarantees the old socket peer is gone and a distinct daemon has answered ping with the expected code signature.

Python hot reloads safely complete an in-flight watch/wait with server_restarted=true before replacing the MCP front-end. Agents remain in agentd; reissue one watch for the complete remaining fleet. Mutating calls and server-to-client permission forms are allowed to finish before reload.

Compact watch consumes queued events so long-running sessions stay bounded. Use reasonix_watch(..., detail=true) when raw event/turn detail is wanted immediately. Full accumulated text remains available afterward through reasonix_poll(include_full=true), and tool history through reasonix_transcript.

For diagnostics, the server also emits this custom notification on the wire:

{"method": "reasonix/agent_event", "params": {
  "session_id": "…", "event": "turn_end", "status": "idle",
  "stop_reason": "end_turn", "transcript_path": "…", "note": "…"}}
  • Events: status (plan/current-work update), turn_end (done/stopped/errored, with stop_reason), background_failure_recovery (diagnostic same-session continuation), permission_request (a tool-approval or an agent question — see below), process_exited.

  • Diagnostic only: JSON-RPC clients silently ignore unknown notifications, and even a standard MCP notification is delivered to the client application, not injected into the model's conversation. Do not build orchestration loops around either reasonix/agent_event or notifications/message.

Orchestrator loop: reasonix_watch(session_ids) → handle each result → answer a permission or remove a terminal id → watch the remaining ids again. reasonix_wait remains available as a legacy event-driven, output-sensitive long poll; unlike watch, it requires a follow-up reasonix_poll.

  • Diagnostic notifications are always emitted. The daemon and MCP server log each emit/relay result so host-side dropping is distinguishable from a server-side omission.

The Reasonix [notifications].enabled setting controls Reasonix CLI/desktop system notifications; it is separate from this MCP transport.

Errored turns and unexpected process exits include error_text in the push payload. reasonix_watch returns the same detail directly, so an orchestrator can decide whether to resume or retry without inspecting the session JSONL by hand.

  • Verified at the wire level by selftest_notifications.py (raw JSON-RPC client — the SDK client validates server notifications against known types and may drop custom ones).

Agent questions

The agent can put a structured multiple-choice question to the orchestrator mid-task via its built-in ask tool — and YOLO does not bypass it (a question is a genuine user decision, not a tool approval). Questions ride the same session/request_permission channel (kind "other"), so they surface identically:

{"permission_request": {"request_id": 7,
  "tool_call": {"title": "Which approach should we take?", "kind": "other",
                 "toolCallId": "ask-1-q1", "rawInput": {"id":"q1","options":[…],"question":…}},
  "options": [{"optionId": "q1:1", "name": "A"}, {"optionId": "q1:2", "name": "B"}, …]}}

Clients that advertise MCP elicitation receive a standard elicitation/create form and the selected option is returned to Reasonix automatically. If the client declines or cancels that form, Reasonix leaves the ACP request pending; dismissal is not treated as rejection. Watch and answer with reasonix_respond_permission(session_id, "q1:1"); the chosen label becomes the ask tool result and the agent continues. A diagnostic agent_event is also emitted. Verified live by selftest_question.py.

Spawn defaults

Spawned agents run at effort = max on opencode-go/deepseek-v4-flash with tool_approval = yolo by default (the user's requested defaults). All spawn options are per-call overridable, and the defaults themselves are env-overridable (REASONIX_MCP_DEFAULT_MODEL, REASONIX_MCP_DEFAULT_EFFORT, REASONIX_MCP_DEFAULT_WORK_MODE, REASONIX_MCP_DEFAULT_TOOL_APPROVAL, REASONIX_MCP_DEFAULT_LANGUAGE).

Model selection: pass reasonix_spawn(model="<provider>/<model>", ...) to pick the agent's model per call — reasonix_models() lists the valid refs and each model's supported_efforts (effort is per-model: e.g. kimi-k3 accepts only high/max; an unsupported value fails at spawn). OpenAI-compatible proxy providers must opt into effort with reasoning_protocol = "openai", supported_efforts = [...], and default_effort = "..." at provider or model-override scope. Some gateways instead bake effort into variant model IDs; only those gateways should be selected by suffix. If neither mechanism is configured, spawn reports effort in skipped_options.

reasonix_send is forced steer: a message is always delivered — queued as mid-turn guidance while a turn is running, or submitted as a new turn if the agent is idle (or the turn ended mid-race). Messages are never dropped. expect (any default) narrows that: expect="steer" raises instead of accidentally starting a new turn; expect="new_turn" raises if the message was steered into a running turn.

reasonix_configure changes the model without replacing the logical session or losing its transcript. Reasonix cannot rebuild a model while a turn or background job is active, so an active agent reports queued=true and keeps the old model for that turn; the new configuration is applied before the next turn. For a stopped/exited agent, configure first and then call reasonix_resume, or pass the model/options directly to resume. Desired configuration is persisted in a per-session sidecar so daemon/MCP restarts do not lose it.

The same tool changes tool_approval (ask, auto, or yolo) at any point. Idle agents change immediately; active agents use the new mode from their next turn. To avoid deadlock, changing ask to yolo while a tool permission is pending selects its non-persistent allow_once option and auto-allows further tool gates for the remainder of that turn. It never answers an agent's explicit question (kind="other"). Changing to auto leaves an existing decision pending because automatic policy is not equivalent to unconditional approval. During a rolling update, an already-running older shared agentd may not yet have the configure RPC because other orchestrators still have active agents. The hot-reloaded MCP front-end transparently persists all requested options; it emulates yolo tool approvals immediately and reports other options as waiting for the safe daemon reload instead of returning unknown method.

Option

Values

model

any configured provider/model, e.g. opencode-go/deepseek-v4-flash

effort

Model-advertised values: auto · disabled · low · medium · high · xhigh · max

work_mode

economy (lean tool surface) · balanced (complete default) · delivery (requires acceptance criteria + review/verification evidence)

tool_approval

ask · auto · yolo (default)

Poll is lean (orchestrator-friendly)

A spawned session emits a burst of static setup events (available_commands_update — 24 slash commands with descriptions — and config_option_update — the full model catalogue). Unfiltered, that is ~31 KB / ~8k tokens per poll for a 4-byte reply (measured), which kills parallel orchestration. By default reasonix_poll omits the entire diagnostic event stream and returns only orchestration state: status, a capped message when output changed, stop_reason, plan, current_work, pending permission, and errors. Empty and unchanged metadata is omitted. Use detail=true to retrieve the prior detailed shape. In that shape, the two static setup types are still filtered and events_filtered counts what was omitted. To change the filter:

  • include_events=["tool_call","tool_call_update","plan"] — only these sessionUpdate types (permission requests are always included);

  • Detailed polls return a small recent event tail (50 by default; set max_events to choose another value). include_events opts named types back in, including static setup types when explicitly named.

  • exclude_events=[...] — drop additional types;

  • the orchestrator-relevant set is tool_call, tool_call_update, plan, permission_request.

Poll includes current_work when there is active work: either the active native tool call or the plan step marked in_progress. Agents spawned through this server receive a small status contract asking them to keep that plan current. It is injected exactly once per persisted session: follow-up turns do not repeat it, and resume detects it in session history. Explicit task restrictions on tools take precedence.

Reasonix MCP defaults native language auto-detection to English by passing REASONIX_LANG=en to each child. Set REASONIX_MCP_DEFAULT_LANGUAGE to auto|en|zh before starting agentd to change that default. An explicit top-level language in Reasonix config remains authoritative; visible reasoning is controlled separately by [agent].reasoning_language. The MCP does not inject a competing language prompt.

turns in detailed poll results gives completed turns as [{text, stop_reason}] — clean turn boundaries (full_text alone concatenates turns).

Thought and full_* are opt-in. Reasoning is the bulk of what effort=max models emit, so ordinary reasonix_poll does not return thought, full_thought, full_text, text, turns, or events. Accumulated output stays server-side and is available on demand:

  • include_thought=Truethought (delta) + full_thought

  • include_full=Truefull_text (whole conversation; turns usually suffices — per-turn text + stop_reason)

Use include_thought when diagnosing a derailed agent. Defaults are env-overridable: REASONIX_MCP_INCLUDE_THOUGHT=1, REASONIX_MCP_INCLUDE_FULL=1.

Parallel orchestration

spawn 6–8 agents (note session_ids, each spawn reports its sandbox posture)
loop:
  event = reasonix_watch(all ids)      # no timeout; compact terminal/permission results
  handle event.results                 # no follow-up poll
  reasonix_send(sid, msg, expect="steer") when a discovery invalidates a round
  drop finished ids from the watch list; reasonix_stop() the rest when done
reasonix_list(pending_only=true) whenever you lose track of session_ids

Caps & truncation

Poll output is capped so long gaps can't blow up the MCP host's context; the cuts are reported, never silent:

Field

Limit (env override)

events

recent tail (50 by default; explicit cap 200) — events_dropped counts the cut

compact poll message

first/last MAX_WATCH_MESSAGE (4k) chars — message_truncated

detailed text

last MAX_DELTA_TEXT (100k) chars — text_truncated

compact watch message

first/last MAX_WATCH_MESSAGE (4k) chars — message_truncated

thought / full_thought (only with include_thought)

last MAX_FULL_TEXT (200k) chars — *_truncated

full_text (only with include_full)

last MAX_FULL_TEXT (200k) chars — full_text_truncated

events_dropped (queue/size cap) is distinct from events_filtered (static-type omission). The unpolled in-server event buffer is bounded at 4000 chunk events; critical events (permission, turn end, process exit) are never dropped.

Sandbox posture

reasonix_spawn reads and returns the effective [sandbox] posture at spawn time (sandbox, legacy bash, allow_write, network, workspace_root, config_file) so an orchestrator knows up front whether agents can execute commands and write outside cwd. Note Reasonix semantics: bash = "off" means unconfined (execution allowed), while bash = "enforce" jails commands in bubblewrap when available. The clearer posture names are sandbox = "bwrap" or sandbox = "none"; with none, allow_write cannot be enforced for bash and a warning is returned/logged. Sandbox posture itself is fixed for the ACP process; session options requested during a turn apply before its next turn. Inspect the spawn response for the effective sandbox posture. Under tool_approval = "ask", gated commands raise a permission_request in watch/poll — answer with reasonix_respond_permission; approving blind is not required: the request's tool_call carries the tool name (title/kind) and rawInput (the JSON arguments). Ask mode does not pause every shell command: Reasonix requests permission only for commands its policy classifies as gated. Its explicit ask tool always creates a user-decision request, including in YOLO mode.

Updating the daemon

agentd watches agentd.py, acp_bridge.py, and common.py. A source change queues an automatic restart; active turns, pending decisions, and keep_alive=true sessions are never interrupted. Once agents are safely idle and their terminal output has been polled, the daemon waits a short grace period, closes resumable idle processes, and starts a fresh daemon from current code. No MCP reinstallation is needed.

For explicit control, call reasonix_restart_agentd(). It safely reloads the shared daemon when no live agents remain. If live agents can be discarded, use reasonix_restart_agentd(force=true); their persisted transcripts can be resumed after the fresh daemon starts. The next tool call automatically starts the new daemon, so no shell command or socket cleanup is needed. The daemon is shared by MCP clients, so a restart disconnects other clients too; they reconnect automatically on their next tool call.

Restarting the MCP server

launcher.py watches the package's Python sources and replaces its MCP server automatically after a change. It proxies stdio, replays the negotiated MCP handshake into the new child, and leaves shared agentd sessions untouched, so an already-running orchestrator can continue without being restarted. Every orchestrator has its own launcher and refreshes independently.

reasonix_restart_mcp_server() remains available for an explicit reload. Registrations that point directly to server.py must be changed to launcher.py once; after that, source updates need no MCP reinstall.

Orchestrator isolation

Sessions are scoped to the MCP orchestrator conversation that created them. Codex thread IDs (and equivalent host session IDs when provided) keep two conversations in the same project isolated while allowing that conversation to reconnect after an IDE restart. reasonix_list and all session operations only expose that orchestrator's sessions. The scope is stable across MCP server restarts and is derived from the MCP client's name and workspace. If multiple instances of the same CLI run in the same workspace, set a distinct stable value in each environment:

REASONIX_MCP_ORCHESTRATOR_ID=project-a-cli-1

The daemon is shared, but ownership is enforced by the daemon and persisted with each session. reasonix_restart_agentd remains a global operation because restarting the shared daemon affects every orchestrator; it refuses while another orchestrator has live agents unless force=true.

Agent cleanup

After a terminal turn (including an errored turn), an agent remains available for the idle grace period (REASONIX_MCP_IDLE_TIMEOUT, disabled by default, or idle_timeout per spawn) so the orchestrator can poll its final output or send a quick follow-up. It is then stopped and remains as an exited, resumable tombstone. Set keep_alive=true on reasonix_spawn for an agent that needs ongoing interactive turns; call reasonix_stop when it is no longer needed.

Use idle_timeout=-1 to disable cleanup, 0 for immediate cleanup after a terminal turn, or a positive number of seconds for a grace period.

Safety

  • Agents run under your Reasonix permissions and workspace sandbox (writes confined to cwd + allow_write; bash jailed where the OS sandbox is enabled and available). With sandbox = "none"/bash = "off", bash is unjailed and allow_write is not enforceable.

  • Spawn cwd is confined: reasonix_spawn(cwd=…) is rejected unless the target is the MCP host's project dir (or a subdir) or one of the [sandbox] allow_write dirs — a prompt-injected orchestrator can't spawn agents that write anywhere (the server runs with your full permissions and yolo default). Escape hatch: REASONIX_MCP_ALLOW_ANY_CWD=1.

  • Agents die with the server: PDEATHSIG guarantees a killed MCP server can never orphan reasonix acp processes.

  • tool_approval per spawn: yolo (default; approve except protected decisions), auto (follow configured permission rules), or ask (relay every approval through reasonix_respond_permission).

  • reasonix_spawn returns the effective sandbox posture — check it before assigning tasks that require running commands.

  • cwd defaults to the MCP host's project root; pass an explicit cwd to scope an agent elsewhere.

Long-running agents

A spawned agent may legitimately run for hours — long thinking, tool loops, implementing across many files. The bridge is built for that:

  • Nothing blocks. reasonix_spawn / reasonix_send / reasonix_poll return immediately; the agent works in its own subprocess. The MCP host stays fully responsive while the agent grinds.

  • No default timeout. A turn runs until it finishes or you call reasonix_stop (cancels the turn, closes the session, kills the process).

  • You can come back anytime. reasonix_poll reports status: "running" with a capped new message and current plan/work — leave it for an hour, then check again. reasonix_send steers even mid-turn.

  • Memory is bounded. Unpolled chunk events are capped in the server; poll caps compact messages, while opt-in detailed results cap text/thought/full_text and the structured events list (tails kept, *_truncated / events_dropped report the cut).

One constraint, now mostly lifted: agents live in the daemon, not the MCP server — the MCP host can close and come back and the fleet is still running (reasonix_list). The daemon itself is the process survival boundary: kill it and its agents die (PDEATHSIG). Active sessions are checkpointed as orphaned, their work survives on disk, and one session or a whole fleet can be revived with reasonix_resume / reasonix_resume_fleet.

Testing

.venv/bin/python tests/selftest.py              # spawn → poll → steer → stop (real provider)
.venv/bin/python tests/selftest_daemon.py       # survival across server kill + resume + 6-way concurrency (real provider)
.venv/bin/python tests/selftest_permission.py   # ask-mode permission round-trip (real provider)
.venv/bin/python tests/selftest_question.py     # agent asks a question via `ask` (real provider)
.venv/bin/python tests/selftest_transcript.py   # transcript + plan fields (real provider)
.venv/bin/python tests/selftest_orchestrator.py # list/wait/filtering/posture (no model calls)
.venv/bin/python tests/selftest_notifications.py # diagnostic event frames (no model calls)
.venv/bin/python tests/selftest_elicitation.py    # ACP decision → MCP elicitation bridge (no model calls)
.venv/bin/python tests/selftest_watch.py          # watch overlap/cancellation safety (no model calls)
.venv/bin/python tests/selftest_recovery.py       # dead-child + durable fleet recovery (no model calls)
.venv/bin/python tests/selftest_agentd_restart_escalation.py # lying shutdown → TERM → verified replacement
.venv/bin/python tests/selftest_runtime_health.py # workspace-blocked vs process-stalled classification
.venv/bin/python tests/selftest_mcp_restart.py   # manual + source-change hot reload (no model calls)
.venv/bin/python tests/selftest_auto_reload.py   # agentd source watcher + self-replace (no model calls)
.venv/bin/python tests/selftest_prompt_injection.py # one status contract per session (no model calls)
.venv/bin/python tests/selftest_background_recovery.py # bounded failed-background-job continuation
.venv/bin/python tests/selftest_chaos.py       # cwd allowlist + dual notify + PDEATHSIG (no model calls)
.venv/bin/python tests/selftest_allow_write.py  # cross-cwd write via allow_write (real provider)

The selftest runs fully isolated: it copies config.toml + .env into a scratch REASONIX_HOME under /tmp (removed on exit), so it never touches your live ~/.reasonix sessions and spawns the native Reasonix Go binary directly (never the npm node shim) in its own process group that it kills on cleanup.

Related MCP Connectors

Related MCP Servers

  • F
    license
    Not graded
    quality
    Not graded
    maintenance
    Enables delegation of immediate conversational messages and long-running background tasks to OpenClaw agents. It allows users to spawn research or monitoring tasks, check execution status, and manage active agent sessions within MCP-compatible clients.
    -
  • F
    license
    A
    quality
    F
    maintenance
    MCP server that exposes 300+ AI agents as tools via a single API key. Supports listing agents, invoking any agent with chat-completion style messages, checking agent health, and retrieving platform statistics.
    5
    4
    -