Skip to main content

Controller

Trait Controller 

Source
pub trait Controller: Send + Sync {
Show 15 methods // Required methods fn start( &self, block_meta: HashMap<String, Value>, rt_opts: Option<Value>, force: bool, ) -> Result<(), String>; fn stop(&self, graceful: bool, new_status: &str) -> Result<(), String>; fn get_runtime_status(&self) -> BlockControllerRuntimeStatus; fn send_input( &self, input: BlockInputUnion, seq: Option<u64>, ) -> Result<(), String>; fn controller_type(&self) -> &str; fn block_id(&self) -> &str; fn as_any(&self) -> &dyn Any; // Provided methods fn stop_for_replace(&self, new_status: &str) -> Result<(), String> { ... } fn shutdown( &self, _deadline: Instant, ) -> Pin<Box<dyn Future<Output = StopOutcome> + Send>> { ... } fn agent_id(&self) -> Option<String> { ... } fn turn_provenance(&self) -> Option<TurnProvenance> { ... } fn turn_tracker(&self) -> Option<Arc<TurnActivityTracker>> { ... } fn set_agent_id(&self, _id: Option<String>) { ... } fn stable_agent_id(&self) -> Option<String> { ... } fn stable_agent_uid(&self) -> Option<String> { ... }
}
Expand description

Trait for block controllers. Each block type has its own implementation. Port of Go’s blockcontroller.Controller interface.

Required Methods§

Source

fn start( &self, block_meta: HashMap<String, Value>, rt_opts: Option<Value>, force: bool, ) -> Result<(), String>

Start the controller. May spawn background tasks. force restarts even if already running.

Source

fn stop(&self, graceful: bool, new_status: &str) -> Result<(), String>

Stop the controller. graceful waits for process to exit; new_status is the target state.

Source

fn get_runtime_status(&self) -> BlockControllerRuntimeStatus

Get the current runtime status.

Source

fn send_input( &self, input: BlockInputUnion, seq: Option<u64>, ) -> Result<(), String>

Send input (terminal data, signal, or resize) to the controller. seq is the per-TermViewModel monotonic counter; None means fire-and-forget (no ordering).

Source

fn controller_type(&self) -> &str

Get the controller type (e.g., “shell”, “cmd”).

Source

fn block_id(&self) -> &str

Get the block ID.

Source

fn as_any(&self) -> &dyn Any

Downcast support for concrete controller types.

Provided Methods§

Source

fn stop_for_replace(&self, new_status: &str) -> Result<(), String>

Stop this controller because it’s being REPLACED by a new one for the same block (session restart / resync’s needs_replace path), NOT because the block itself is being closed. Must terminate this controller’s own CLI process so it doesn’t linger, but — unlike stop() — must NOT reach any declared-background descendant the process may have spawned (e.g. a task dev instance launched via run_in_background: true); those must survive the replace. See docs/specs/SPEC_BACKGROUND_TASK_TEARDOWN_SURVIVAL_2026_08_20.md.

Default delegates to stop() — correct for any controller type with no subprocess tree of its own to be careful about (nothing to preserve, so the two calls are equivalent). Only ShellController overrides this today.

Source

fn shutdown( &self, _deadline: Instant, ) -> Pin<Box<dyn Future<Output = StopOutcome> + Send>>

Stop gracefully because the block is being CLOSED: end any turn in progress, ask the process to exit, force-kill at deadline. The returned future resolves once the process has actually exited (or there was none). Does NOT drop the block’s process tracker — the caller does that after this resolves, so nothing the agent started outlives it. See docs/specs/SPEC_AGENT_PANE_CLOSE_GRACEFUL_SHUTDOWN_2026_09_18.md §9.3.

Default: today’s immediate stop. Right for controllers with no agent session worth ending cleanly (shells, cmd, per-turn subprocess) and for providers whose exit behaviour hasn’t been measured yet (ACP, app-server — §9.3 requires that before they get a graceful path).

Source

fn agent_id(&self) -> Option<String>

This block’s own live, spawn-time-captured jekt/muxbus identity, if it has one — an independent source of truth for the recipient- identity check in ReactiveHandler::inject_message_inner, deliberately NOT derived from ReactiveHandler’s own name_to_blocks/agent_info maps (checking a registry against itself would be a tautology and catch nothing). Default None for controller types that aren’t jekt-addressable at all (e.g. plain terminals) — only ShellController and PersistentSubprocessController currently override this, mirroring the two paths that already resolve a jekt-registration identity at spawn time (resolve_agent_id_for_jekt, muxbus_agent_id_from_env).

Source

fn turn_provenance(&self) -> Option<TurnProvenance>

What started this agent’s current turn (SPEC_AGENT_SELF_QUIT §6.3), if the controller tracks it. None = unknown, which never counts as the user — the safe answer for controllers that can’t tell (ACP, App Server, subprocess: both origins reach them through one method).

Source

fn turn_tracker(&self) -> Option<Arc<TurnActivityTracker>>

This controller’s turn tracker, for the turn ledger’s bookkeeping from outside the controller (a held message’s join hint). None for controllers that track no turns.

Source

fn set_agent_id(&self, _id: Option<String>)

Refresh this block’s own captured jekt/muxbus identity (see agent_id’s doc comment). Called whenever ReactiveHandler::register_agent/register_agent_with_nonce (re-)registers THIS block’s block_id under a (possibly different) agent_id, so the two independently-written copies never drift apart (#2697: agent_id() was captured once at spawn and never refreshed, while the registry’s name bindings get re-keyed on every register_agent call — e.g. handle_reactive_register’s frontend-initiated HTTP path — causing a legitimately renamed or reconfigured agent’s own messages to be falsely rejected as an identity mismatch). Default no-op for controller types that don’t override agent_id either.

Source

fn stable_agent_id(&self) -> Option<String>

This block’s STABLE jekt identity — AGENTMUX_AGENT_ID as captured once at spawn, never refreshed. Distinct from agent_id, which tracks the LIVE, renameable display name and is deliberately refreshed every turn (see that method’s doc comment on #2697). AGENTMUX_AGENT_ID is embedded verbatim in PR-body tags specifically because it does NOT change if the agent is later renamed — but ReactiveHandler’s registry only tracks one key per block, and every turn’s agent_id() refresh evicts whatever this block was previously registered under. ReactiveHandler::inject_message_inner’s recipient-identity check (#2695) uses this alongside agent_id() so a jekt addressed to the stable ID is validated correctly even after the primary registry key has moved on to the live display name. See INCIDENT_2026_09_09_JEKT_STABLE_ID_ALIAS.md. Default None, mirroring agent_id’s default — only PersistentSubprocessController currently overrides this.

Source

fn stable_agent_uid(&self) -> Option<String>

Identity M2: this block’s UID (db_agents.id) as carried in its spawn env (AGENTMUX_AGENT_UID, set by build_persistent_spawn_env since M1a), captured once at spawn like [stable_agent_id]. Backs the registry’s UID confirmer: consulted only for targets that resolved by UID, and None — a registered-but-not-yet-spawned controller, or a block with no row — is “unverifiable”, not a mismatch. Default None; only PersistentSubprocessController overrides this.

Dyn Compatibility§

This trait is dyn compatible.

In older versions of Rust, dyn compatibility was called "object safety".

Implementors§