pub trait Agent: Send + Sync {
// Required methods
fn launch_command(&self, session: &Session) -> String;
fn environment(&self, session: &Session) -> Vec<(String, String)>;
fn initial_prompt(&self, session: &Session) -> String;
// Provided methods
fn system_prompt(&self) -> Option<String> { ... }
fn detect_activity<'life0, 'life1, 'async_trait>(
&'life0 self,
session: &'life1 Session,
) -> Pin<Box<dyn Future<Output = Result<ActivityState>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait { ... }
fn cost_estimate<'life0, 'life1, 'async_trait>(
&'life0 self,
session: &'life1 Session,
) -> Pin<Box<dyn Future<Output = Result<Option<CostEstimate>>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait { ... }
}Expand description
A specific AI coding tool (Claude Code, Codex, Aider, Cursor, …).
Mostly a metadata provider (launch command, env, prompt), plus one async
hook — detect_activity — which the lifecycle loop polls to learn what
the underlying agent process is currently doing. The TS reference does
this by tailing a JSONL log file the agent writes; Slice 1 Phase C’s
stub just returns Ready so the polling loop has something to drive.
Required Methods§
Sourcefn launch_command(&self, session: &Session) -> String
fn launch_command(&self, session: &Session) -> String
Single shell string the runtime will run inside its execution context.
fn environment(&self, session: &Session) -> Vec<(String, String)>
Sourcefn initial_prompt(&self, session: &Session) -> String
fn initial_prompt(&self, session: &Session) -> String
First prompt to deliver after the process is up.
Provided Methods§
Sourcefn system_prompt(&self) -> Option<String>
fn system_prompt(&self) -> Option<String>
System rules / workflow guidance that must be prepended to the user prompt by the caller.
Agents that support a dedicated system-prompt CLI flag (e.g.
Claude Code’s --append-system-prompt) inject rules at launch
time via launch_command and return
None here. Agents without such a flag (e.g. Cursor) return
Some(rules) so callers composing a richer prompt via
build_prompt can prepend
them before delivery.
Default: None — no system prompt to inject separately.
Sourcefn detect_activity<'life0, 'life1, 'async_trait>(
&'life0 self,
session: &'life1 Session,
) -> Pin<Box<dyn Future<Output = Result<ActivityState>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
fn detect_activity<'life0, 'life1, 'async_trait>(
&'life0 self,
session: &'life1 Session,
) -> Pin<Box<dyn Future<Output = Result<ActivityState>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
Inspect whatever evidence this agent leaves behind (log files, terminal scrollback, pid probes, …) and report its current activity state. Called once per lifecycle tick.
The default impl consults {workspace}/.ao/activity.jsonl via
activity_log::detect_activity_from_log and surfaces:
Exitedwhen the last entry is terminal (no staleness downgrade — exit is a one-way signal).WaitingInput/Blockedwhen the last entry is actionable and fresh (withinACTIVITY_INPUT_STALENESS_SECS).
Falls back to Ready when there’s no workspace, no log, or the
log only carries noisy signals (Active / Ready / Idle /
stale actionable). Plugins with richer native detection (JSONL
tailing, git-index mtime, …) override this entirely.
Sourcefn cost_estimate<'life0, 'life1, 'async_trait>(
&'life0 self,
session: &'life1 Session,
) -> Pin<Box<dyn Future<Output = Result<Option<CostEstimate>>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
fn cost_estimate<'life0, 'life1, 'async_trait>(
&'life0 self,
session: &'life1 Session,
) -> Pin<Box<dyn Future<Output = Result<Option<CostEstimate>>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
Poll current aggregated token usage / cost from the agent’s logs.
Called by the lifecycle loop when a session’s status changes (not
every tick). The default impl consults
{workspace}/.ao/usage.jsonl via cost_log::parse_usage_jsonl
and returns the aggregate when entries exist. Returns None
when cost tracking is unavailable — either because there’s no
workspace, the file is missing, or it aggregates to zero tokens.
Plugins with native cost sources (e.g. agent-claude-code
reading ~/.claude/projects/**) override this.