feat: change multi-agent to use path-like system instead of uuids (#15313)

This PR add an URI-based system to reference agents within a tree. This
comes from a sync between research and engineering.

The main agent (the one manually spawned by a user) is always called
`/root`. Any sub-agent spawned by it will be `/root/agent_1` for example
where `agent_1` is chosen by the model.

Any agent can contact any agents using the path.

Paths can be used either in absolute or relative to the calling agents

Resume is not supported for now on this new path
This commit is contained in:
jif-oai
2026-03-20 18:23:48 +00:00
committed by GitHub
Unverified
parent 4ddde54c19
commit 79ad7b247b
57 changed files with 1707 additions and 299 deletions
@@ -69,6 +69,8 @@ pub struct ThreadMetadata {
pub agent_nickname: Option<String>,
/// Optional role (agent_role) assigned to an AgentControl-spawned sub-agent.
pub agent_role: Option<String>,
/// Optional canonical agent path assigned to an AgentControl-spawned sub-agent.
pub agent_path: Option<String>,
/// The model provider identifier.
pub model_provider: String,
/// The latest observed model for the thread.
@@ -116,6 +118,8 @@ pub struct ThreadMetadataBuilder {
pub agent_nickname: Option<String>,
/// Optional role (agent_role) assigned to the session.
pub agent_role: Option<String>,
/// Optional canonical agent path assigned to the session.
pub agent_path: Option<String>,
/// The model provider identifier, if known.
pub model_provider: Option<String>,
/// The working directory for the thread.
@@ -152,6 +156,7 @@ impl ThreadMetadataBuilder {
source,
agent_nickname: None,
agent_role: None,
agent_path: None,
model_provider: None,
cwd: PathBuf::new(),
cli_version: None,
@@ -182,6 +187,10 @@ impl ThreadMetadataBuilder {
source,
agent_nickname: self.agent_nickname.clone(),
agent_role: self.agent_role.clone(),
agent_path: self
.agent_path
.clone()
.or_else(|| self.source.get_agent_path().map(Into::into)),
model_provider: self
.model_provider
.clone()
@@ -241,6 +250,9 @@ impl ThreadMetadata {
if self.agent_role != other.agent_role {
diffs.push("agent_role");
}
if self.agent_path != other.agent_path {
diffs.push("agent_path");
}
if self.model_provider != other.model_provider {
diffs.push("model_provider");
}
@@ -300,6 +312,7 @@ pub(crate) struct ThreadRow {
source: String,
agent_nickname: Option<String>,
agent_role: Option<String>,
agent_path: Option<String>,
model_provider: String,
model: Option<String>,
reasoning_effort: Option<String>,
@@ -326,6 +339,7 @@ impl ThreadRow {
source: row.try_get("source")?,
agent_nickname: row.try_get("agent_nickname")?,
agent_role: row.try_get("agent_role")?,
agent_path: row.try_get("agent_path")?,
model_provider: row.try_get("model_provider")?,
model: row.try_get("model")?,
reasoning_effort: row.try_get("reasoning_effort")?,
@@ -356,6 +370,7 @@ impl TryFrom<ThreadRow> for ThreadMetadata {
source,
agent_nickname,
agent_role,
agent_path,
model_provider,
model,
reasoning_effort,
@@ -379,6 +394,7 @@ impl TryFrom<ThreadRow> for ThreadMetadata {
source,
agent_nickname,
agent_role,
agent_path,
model_provider,
model,
reasoning_effort: reasoning_effort
@@ -447,6 +463,7 @@ mod tests {
source: "cli".to_string(),
agent_nickname: None,
agent_role: None,
agent_path: None,
model_provider: "openai".to_string(),
model: Some("gpt-5".to_string()),
reasoning_effort: reasoning_effort.map(str::to_string),
@@ -474,6 +491,7 @@ mod tests {
source: "cli".to_string(),
agent_nickname: None,
agent_role: None,
agent_path: None,
model_provider: "openai".to_string(),
model: Some("gpt-5".to_string()),
reasoning_effort,