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§
Sourcefn start(
&self,
block_meta: HashMap<String, Value>,
rt_opts: Option<Value>,
force: bool,
) -> Result<(), String>
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.
Sourcefn stop(&self, graceful: bool, new_status: &str) -> Result<(), String>
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.
Sourcefn get_runtime_status(&self) -> BlockControllerRuntimeStatus
fn get_runtime_status(&self) -> BlockControllerRuntimeStatus
Get the current runtime status.
Sourcefn send_input(
&self,
input: BlockInputUnion,
seq: Option<u64>,
) -> Result<(), String>
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).
Sourcefn controller_type(&self) -> &str
fn controller_type(&self) -> &str
Get the controller type (e.g., “shell”, “cmd”).
Provided Methods§
Sourcefn stop_for_replace(&self, new_status: &str) -> Result<(), String>
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.
Sourcefn shutdown(
&self,
_deadline: Instant,
) -> Pin<Box<dyn Future<Output = StopOutcome> + Send>>
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).
Sourcefn agent_id(&self) -> Option<String>
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).
Sourcefn turn_provenance(&self) -> Option<TurnProvenance>
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).
Sourcefn turn_tracker(&self) -> Option<Arc<TurnActivityTracker>>
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.
Sourcefn set_agent_id(&self, _id: Option<String>)
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.
Sourcefn stable_agent_id(&self) -> Option<String>
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.
Sourcefn stable_agent_uid(&self) -> Option<String>
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".