Status: Proposed
Author: Codex draft for discussion
Date: 2026-04-08
Add a small, pod-only runtime lifecycle surface centered on two optional callbacks:
mount/2shutdown/2
These callbacks would live on Jido.Pod modules, not on Jido.Agent, and
would run in the pod runtime rather than during pure agent construction.
The main motivation is to support pods whose effective topology is only known at runtime, for example when a pod definition lives in a database and must be loaded by key before the pod can reconcile.
Jido today has a clear separation between pure agent logic and runtime execution, but there is still an ergonomics gap for pod-oriented applications.
Current pod behavior works well when:
- topology is known at compile time
- topology starts empty and is grown later through
Jido.Pod.mutate/3
That leaves a gap for workflows like:
- load a pod definition from a database
- derive topology from an external config document
- start many keyed pod instances from the same generic pod module
- clean up external resources when the pod manager shuts down
The existing lifecycle seams do not line up cleanly with that use case:
Jido.Agentis intentionally pure- plugin
mount/2is pure initialization duringnew/1 - strategy
init/2exists, but it is strategy-oriented and not pod-specific AgentServerlifecycle hooks are internal runtime seams, not a pod API
This makes it harder to answer a basic user question: "where should pod startup and cleanup logic go?"
- Give pod authors one obvious place to put pod-specific runtime startup logic.
- Support runtime-defined topology loading without adding impure callbacks to
Jido.Agent. - Support best-effort pod-specific cleanup on shutdown.
- Keep the initial API small and legible.
- Preserve the current mental model that pod topology remains durable state.
- Do not add generic runtime lifecycle callbacks to
Jido.Agent. - Do not replace
Jido.Pod.mutate/3as the API for live topology changes. - Do not add per-node start/stop callbacks in the initial proposal.
- Do not make plugin
mount/2impure. - Do not rely on
shutdown/2for correctness-critical teardown semantics.
This proposal is intentionally scoped to pods.
A Jido.Agent value does not really "start" or "stop" in the runtime sense. It
is created, updated, checkpointed, and restored. The runtime lifecycle belongs
to AgentServer and, for durable keyed runtimes, to InstanceManager.
Pods already introduce a higher-level runtime concept:
- a durable topology snapshot
- eager reconcile on acquisition
- lazy node activation
- live topology mutation
That makes pods the right place to add a runtime lifecycle surface.
use Jido.Pod would define two new optional callbacks on the pod module:
@callback mount(
agent :: Jido.Agent.t(),
ctx :: Jido.Pod.Lifecycle.MountContext.t()
) ::
{:ok, Jido.Agent.t()} | {:error, term()}
@callback shutdown(
agent :: Jido.Agent.t(),
ctx :: Jido.Pod.Lifecycle.ShutdownContext.t()
) ::
:ok | {:error, term()}The callback context should be explicit, structured, and public, not a loose
map().
MountContext should include at least:
pod_idpod_modulejidopartitionserver_pidphase-:freshor:restoredsource- e.g.:agent_server,:instance_manager,:pod_getopts- normalized startup opts when relevant
ShutdownContext should include at least:
pod_idpod_modulejidopartitionserver_pidreason- current topology snapshot
- current node snapshots when available
mount/2 is the primary lifecycle hook in this proposal.
It should run:
- after the pod runtime has started
- after any restore/thaw step has completed
- before the pod is considered ready
Its job is to finalize the effective pod state for this runtime start.
Typical mount/2 responsibilities:
- load a definition document from a database
- build a
%Jido.Pod.Topology{} - replace or update the pod's persisted topology snapshot
- attach pod metadata such as definition id or definition version
- perform pod-specific startup side effects if needed
Typical non-responsibilities:
- do not directly reconcile nodes
- do not manually start child runtimes that should be managed by pod reconcile
- do not act as a replacement for
Jido.Pod.mutate/3
The recommended way to update topology inside mount/2 is to reuse the
existing pure helpers:
Jido.Pod.put_topology/2Jido.Pod.update_topology/2
mount/2 should be called exactly once per runtime start.
mount/2 should receive phase: :fresh | :restored.
This avoids the need for a separate resume callback in the initial design.
Recommended behavior:
:fresh- materialize the initial runtime-defined topology:restored- usually validate or no-op, leaving durable topology intact
The framework should not silently reload a definition on every reacquire. If a pod definition needs to change after startup, that should remain an explicit action through mutation or a future sync API.
If mount/2 returns {:error, reason}, pod startup should fail.
That is preferable to starting a pod manager in a partially initialized state with an unknown or invalid topology.
mount/2 should not return directives in the initial proposal.
Returning directives would make startup ordering significantly harder to reason about:
- whether directives run before or after readiness
- whether directive failures abort startup
- how they interact with reconcile ordering
- whether pod startup should synthesize a special startup signal
For the motivating use cases, mount/2 does not need directives. It already
runs in runtime context and can perform pod-specific I/O directly before
returning an updated agent.
shutdown/2 is a best-effort runtime cleanup hook.
Typical shutdown/2 responsibilities:
- release external leases
- notify an external system that a run ended
- delete temporary resources
- emit an audit or completion event
Typical non-responsibilities:
- do not rely on it to guarantee durable correctness
- do not rely on it as the only way to stop pod children
- do not treat it as a substitute for explicit pod teardown APIs
If an application needs deterministic stop behavior for the whole pod tree, that
should remain an explicit runtime API such as teardown_runtime/2, not a
callback side effect.
If shutdown/2 returns {:error, reason}, the runtime should log and emit
telemetry, but should not block process shutdown.
shutdown/2 should not return directives.
By the time shutdown/2 runs, the pod manager is already on the shutdown path.
Allowing directives here would suggest stronger delivery guarantees than the
runtime can realistically provide.
The callbacks should be defined on the pod module itself:
defmodule MyApp.WorkflowRunPod do
use Jido.Pod, name: "workflow_run"
@impl true
def mount(agent, ctx) do
case MyApp.PodDefinitions.fetch(ctx.pod_id) do
{:ok, definition} ->
topology = MyApp.PodDefinitions.to_topology(definition)
with {:ok, agent} <- Jido.Pod.put_topology(agent, topology) do
{:ok, put_in(agent.state[:__pod__][:metadata][:definition_version], definition.version)}
end
{:error, reason} ->
{:error, reason}
end
end
@impl true
def shutdown(_agent, ctx) do
MyApp.RunTracker.finish(ctx.pod_id, ctx.reason)
:ok
end
endThis keeps pod-specific lifecycle logic in one discoverable place.
This proposal should apply to any runtime start of a pod module, not only
Jido.Pod.get/3.
- acquire pod runtime
- run
mount/2 - if successful, run pod reconcile
This remains the default happy path.
- acquire pod runtime
- run
mount/2 - do not implicitly reconcile
This preserves the current distinction between raw acquisition and the pod happy path helper.
- start pod runtime
- run
mount/2 - do not implicitly reconcile
This keeps direct runtime starts available for tests and low-level use without changing reconcile behavior.
The initial callback surface should stay small.
Good candidates for the first cut:
mount/2shutdown/2
Possible future callbacks, if demand appears:
before_mutate/2after_mutate/2
Callbacks that should stay out of scope for now:
before_reconcileafter_reconcilebefore_ensure_nodeafter_ensure_node- per-node start/stop callbacks
Those lower-level runtime phases are already observable through pod telemetry, and adding hooks there would increase complexity quickly.
Rejected.
This would blur the boundary between pure agent logic and runtime lifecycle and would make pod-specific needs leak into the generic agent abstraction.
Rejected.
Plugin mount/2 is intentionally pure and runs during Agent.new/1. Making it
the place for runtime-defined pod topology loading is a poor fit.
Rejected for this use case.
Strategy lifecycle is about execution strategy state, not durable pod topology materialization.
Possible later, but not preferred for the initial proposal.
One of the main pieces of user feedback is "I am struggling to find where to put things." Putting the callbacks on the pod module itself improves discoverability.
- Should
shutdown/2receive node snapshots by default, or only topology plus basic runtime context? - Should
mount/2run only for pod modules started under keyed lifecycle, or for any pod runtime start? - Should there eventually be an explicit
sync_definition/2API for definition-driven pods that need controlled drift reconciliation?
Start with the smallest useful pod runtime lifecycle API:
- add pod-module
mount/2 - add pod-module
shutdown/2 - use explicit public context structs for both callbacks
- let
mount/2return only{:ok, agent}or{:error, reason} - let
shutdown/2return only:ok | {:error, reason} - do not allow directives from either callback
- invoke
mount/2after restore and before readiness - keep reconcile explicit except for
Jido.Pod.get/3 - document clearly that pod
mount/2is a runtime callback, unlike pluginmount/2
This gives users one obvious place for runtime-defined topology loading and
cleanup without weakening the purity boundary of Jido.Agent.