Skip to content

Commit bc92ec6

Browse files
feat(agent): mutable address routing behind one typed selector
`st2 agent address <agent> <address>` becomes reachable, and every reference plane resolves through the one algorithm decision 0015 mandates instead of each site's own precedence filter. - `message`'s resolution surface takes `&AgentSelector`, not `&str`: recipients, inboxes, state directories, message boxes, and archival all state which namespace they were named in. There is no `&str` arm left to fall back to, so a route can never be silently retyped as an exact subject or the reverse. Recipient matching moves from `bus_id == r || identity == r` to the effective address, with retirement handled by two books rather than a tie-break: a retired subject cannot make a live claimant ambiguous, and still answers to its own address when nothing routable does, keeping its retained state reachable. - Every command that names an agent gains the exact `--id` / `--to-id` form, which resolves without consulting the address namespace at all. - Drivers, channels, and hooks select their own subject by exact key, so an address cutover cannot disconnect a running seat from its own directories. A supervisor reference stays an ordinary address reference. - `event::resolve_stream` routes through `identity::resolve` instead of a hand-rolled three-arm filter, which is also a fix: stream ingress had no dedupe-by-ID and no host pinning, so it did not implement the mandated algorithm. Built-in resync names its recipient by the agent key reconciliation holds, so publication survives a cutover; `st2 event emit` names an ordinary address, so the released spelling refuses at once. - An interrupted send recovers by its record's canonical endpoint, which is an immutable key: a cutover between the pending write and the retry is a route change, not a changed recipient. Durable state is untouched by design. `ST_AGENT`, default task IDs, session socket paths, declaration-parent state, harness records, PTY tags, message provenance, and supervisor edges are all keyed on values this cutover never writes. agent-identity: dev3.direct.omp.v6c4mkm2 agent-persona: generalist agent-supervisor: unavailable agent-tool: OMP agent-tool-version: 18.1.2 agent-runtime: OMP 18.1.2 tooling-profile: dotfiles@7534055
1 parent 9d99c6a commit bc92ec6

13 files changed

Lines changed: 1445 additions & 202 deletions

src/claude_mcp.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ fn channel_content(subject: Option<&str>, body: &str) -> String {
2626
}
2727

2828
pub fn run(catalog_root: &Path, identity: &str) -> Result<()> {
29-
let agent_dir = message::resolve_agent_dir(catalog_root, identity, &crate::run::detect_host())?
29+
let agent_dir = message::resolve_actor_dir(catalog_root, identity, &crate::run::detect_host())?
3030
.with_context(|| format!("Claude MCP agent '{identity}' is not declared"))?;
3131
let inbox = message::inbox_dir(&agent_dir);
3232
let (input_tx, input_rx) = mpsc::channel();

src/claude_session.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ pub fn run(
3030
claude_argv: Vec<String>,
3131
) -> Result<()> {
3232
let agent_dir =
33-
message::resolve_agent_dir(catalog_root, &identity, &crate::run::detect_host())?
33+
message::resolve_actor_dir(catalog_root, &identity, &crate::run::detect_host())?
3434
.with_context(|| format!("Claude driver agent '{identity}' is not declared"))?;
3535
anyhow::ensure!(
3636
!claude_argv.is_empty(),
@@ -159,7 +159,7 @@ pub fn run_observe(
159159
runtime_id: Option<&str>,
160160
event: &str,
161161
) -> Result<()> {
162-
let agent_dir = message::resolve_agent_dir(catalog_root, identity, &crate::run::detect_host())?
162+
let agent_dir = message::resolve_actor_dir(catalog_root, identity, &crate::run::detect_host())?
163163
.with_context(|| format!("Claude driver agent '{identity}' is not declared"))?;
164164
// Counted only once the invocation has its application target: a hook for an undeclared
165165
// agent errors out before any state is applied and must not inflate `hook_invocations_total`.
@@ -464,7 +464,7 @@ pub fn run_statusline(catalog_root: &Path, identity: &str) -> Result<()> {
464464

465465
fn record_statusline(catalog_root: &Path, identity: &str, raw: &[u8]) -> Result<()> {
466466
let payload: serde_json::Value = serde_json::from_slice(raw).unwrap_or(serde_json::Value::Null);
467-
let agent_dir = message::resolve_agent_dir(catalog_root, identity, &crate::run::detect_host())?
467+
let agent_dir = message::resolve_actor_dir(catalog_root, identity, &crate::run::detect_host())?
468468
.with_context(|| format!("Claude driver agent '{identity}' is not declared"))?;
469469
// Deliberately uncounted. The tee builds no telemetry pipeline at all (`DQ-C13`, see
470470
// `main`), so a `record_hook_invocation` here could never reach a collector — and a metric

src/codex_app_server.rs

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ use serde_json::{Value, json};
3131
use sha2::{Digest as _, Sha256};
3232
use tungstenite::{Message as WebSocketMessage, WebSocket};
3333

34-
use crate::{ding, driver_diagnostic, harness_context, harness_state, message, run, status};
34+
use crate::{ding, driver_diagnostic, harness_context, harness_state, identity::AgentSelector, message, run, status};
3535

3636
const REQUIRED_CODEX_CLIENT_REQUESTS: &[&str] = &[
3737
"hooks/list",
@@ -390,7 +390,7 @@ struct CodexDeliveryConfig {
390390
impl CodexDeliveryConfig {
391391
fn resolve(catalog_root: &Path, identity: &str) -> Result<Self> {
392392
let this_host = run::detect_host();
393-
let agent_dir = message::resolve_agent_dir(catalog_root, identity, &this_host)?
393+
let agent_dir = message::resolve_actor_dir(catalog_root, identity, &this_host)?
394394
.with_context(|| {
395395
format!(
396396
"Codex native delivery agent '{identity}' is not declared in {}",
@@ -431,11 +431,23 @@ impl CodexDeliveryConfig {
431431
key_hash.update(body.as_bytes());
432432
let idempotency_key = format!("st2.codex-protocol-rejection.v1:{:x}", key_hash.finalize());
433433
let tags = ["codex-protocol".to_string(), "launch-rejected".to_string()];
434+
// This runtime names itself by exact key, never through its own mutable address.
435+
let sender = match message::actor_selector(&self.catalog_root, &self.identity, &self.this_host)
436+
{
437+
Ok(sender) => sender,
438+
Err(resolve_error) => {
439+
eprintln!(
440+
"st2 codex: failed to resolve agent '{}' as the sender of a protocol rejection report: {resolve_error:#}",
441+
self.identity
442+
);
443+
return;
444+
}
445+
};
434446
if let Err(report_error) = message::send_to_resolved_inbox(
435447
&self.catalog_root,
436-
supervisor,
448+
&AgentSelector::Address(supervisor.to_owned()),
437449
&self.this_host,
438-
&self.identity,
450+
&sender,
439451
Some(&subject),
440452
None,
441453
&tags,

0 commit comments

Comments
 (0)