pub struct LocalProcessRuntime { /* private fields */ }Expand description
An AgentRuntime that runs each agent as a child a2a run --config <path>
OS process.
A first-class adapter (hex rule 6 — not test-only): it gives real process
boundaries on a dev box without Docker, and naturally contains an agent’s
mcp_client arbitrary-command child exec inside that agent’s own process
tree. The supervised binary defaults to the current executable
(new) so the supervisor runs copies of itself; point it
elsewhere with with_exe.
Cheap to clone (shares one map). Children are spawned with kill_on_drop,
so dropping the runtime tears down everything it started.
An agent whose config names its own image ([runtime] image) is refused with
RuntimeError::Unsupported: there is no image to run here, and starting
the config under this binary instead would serve a different agent than the
one that was deployed. Those belong on
ContainerRuntime.
§Logs
By default a child inherits this process’s stdout/stderr, so its output
lands in the supervisor’s terminal and logs has
nothing to read — it reports RuntimeError::Unsupported rather than
pretending the agent was silent. Give it a
log_dir to capture each agent’s output into its own
file instead; that is what makes a2a logs <id> work over this backend, and
it is also what stops a multi-agent control plane from interleaving every
agent’s output into one unlabelled stream.
§Secrets — dev-only isolation
provision enforces the same EnvAllowlist as
ContainerRuntime, so a deployed config cannot
template an un-permitted secret into itself (and thence into its agent card).
But a child process inherits this process’s entire environment, and there
is no allowlist that changes that: an agent whose config declares an
mcp_client with an arbitrary command can read every variable the control
plane holds. That is inherent to process inheritance and is why this adapter
is dev/test-only — deploy untrusted or third-party configs on
ContainerRuntime, where only the allowlisted
variables cross the boundary.
Implementations§
Source§impl LocalProcessRuntime
impl LocalProcessRuntime
Sourcepub fn new() -> Self
pub fn new() -> Self
Supervise copies of the current executable (the a2a binary). Falls back
to "a2a" on PATH if the current exe path can’t be resolved.
Sourcepub fn with_exe(exe: impl Into<PathBuf>) -> Self
pub fn with_exe(exe: impl Into<PathBuf>) -> Self
Supervise a specific a2a binary (e.g. env!("CARGO_BIN_EXE_a2a") in
tests, or a pinned install path).
Sourcepub fn with_log_dir(self, dir: impl Into<PathBuf>) -> Self
pub fn with_log_dir(self, dir: impl Into<PathBuf>) -> Self
Capture each agent’s stdout and stderr into <dir>/<id>.log, making
logs answerable over this backend.
Appends rather than truncates, so restarting an agent keeps the output that explains why it needed restarting.
Sourcepub fn with_allowed_env(self, allowed: EnvAllowlist) -> Self
pub fn with_allowed_env(self, allowed: EnvAllowlist) -> Self
Permit deployed configs to reference these host environment variables.
Anything not listed is rejected at provision.
Note the caveat in the type docs: this bounds what a config may name, not what a child process can read.
Trait Implementations§
Source§impl AgentRuntime for LocalProcessRuntime
impl AgentRuntime for LocalProcessRuntime
Source§fn recover<'life0, 'async_trait>(
&'life0 self,
) -> Pin<Box<dyn Future<Output = Result<Recovered<AgentId>, RuntimeError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
fn recover<'life0, 'async_trait>(
&'life0 self,
) -> Pin<Box<dyn Future<Output = Result<Recovered<AgentId>, RuntimeError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
Always Recovered::Ephemeral: nothing here survives the supervisor.
Children are spawned with kill_on_drop, so an orderly exit takes them
with it, and the only record that they existed was the in-memory table
that died with the process. Even after a SIGKILL leaves orphans behind,
there is nothing durable tying a stray a2a run to an AgentId — so
the honest answer is “I cannot tell you”, not an empty list. This is the
concrete reason the container runtime is the supported control-plane
backend and this one is for dev loops.
Source§fn logs<'life0, 'life1, 'async_trait>(
&'life0 self,
id: &'life1 AgentId,
tail: Option<usize>,
) -> Pin<Box<dyn Future<Output = Result<Vec<String>, RuntimeError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
fn logs<'life0, 'life1, 'async_trait>(
&'life0 self,
id: &'life1 AgentId,
tail: Option<usize>,
) -> Pin<Box<dyn Future<Output = Result<Vec<String>, RuntimeError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
Read back what the child wrote to its captured log file.
Only answerable when this runtime was given a
log_dir; otherwise the output went to the
supervisor’s own streams and is not ours to serve.
Source§fn provision<'life0, 'async_trait>(
&'life0 self,
spec: AgentSpec,
) -> Pin<Box<dyn Future<Output = Result<AgentId, RuntimeError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
fn provision<'life0, 'async_trait>(
&'life0 self,
spec: AgentSpec,
) -> Pin<Box<dyn Future<Output = Result<AgentId, RuntimeError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
AgentId.Source§fn start<'life0, 'life1, 'async_trait>(
&'life0 self,
id: &'life1 AgentId,
) -> Pin<Box<dyn Future<Output = Result<(), RuntimeError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
fn start<'life0, 'life1, 'async_trait>(
&'life0 self,
id: &'life1 AgentId,
) -> Pin<Box<dyn Future<Output = Result<(), RuntimeError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
RuntimeError::AlreadyRunning if it is
already live, RuntimeError::NotFound if it was never provisioned.Source§fn stop<'life0, 'life1, 'async_trait>(
&'life0 self,
id: &'life1 AgentId,
) -> Pin<Box<dyn Future<Output = Result<(), RuntimeError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
fn stop<'life0, 'life1, 'async_trait>(
&'life0 self,
id: &'life1 AgentId,
) -> Pin<Box<dyn Future<Output = Result<(), RuntimeError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
Ok. RuntimeError::NotFound if it was never provisioned.Source§fn health<'life0, 'life1, 'async_trait>(
&'life0 self,
id: &'life1 AgentId,
) -> Pin<Box<dyn Future<Output = Result<RuntimeHealth, RuntimeError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
fn health<'life0, 'life1, 'async_trait>(
&'life0 self,
id: &'life1 AgentId,
) -> Pin<Box<dyn Future<Output = Result<RuntimeHealth, RuntimeError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
RuntimeHealth.Source§impl Clone for LocalProcessRuntime
impl Clone for LocalProcessRuntime
Auto Trait Implementations§
impl !RefUnwindSafe for LocalProcessRuntime
impl !UnwindSafe for LocalProcessRuntime
impl Freeze for LocalProcessRuntime
impl Send for LocalProcessRuntime
impl Sync for LocalProcessRuntime
impl Unpin for LocalProcessRuntime
impl UnsafeUnpin for LocalProcessRuntime
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
Source§fn in_current_span(self) -> Instrumented<Self> ⓘ
fn in_current_span(self) -> Instrumented<Self> ⓘ
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more