Merge fix/prepare-ergonomics — auto-gen agent-id for dogfood workflow

This commit is contained in:
Parfii-bot 2026-04-23 05:31:03 +08:00
commit 3095b325d2

View file

@ -18,6 +18,7 @@ use crate::role::resolve_role;
use anyhow::{anyhow, Context, Result};
use serde::{Deserialize, Serialize};
use std::path::Path;
use std::time::{SystemTime, UNIX_EPOCH};
/// Everything the orchestrator needs to hand the Claude `Agent` tool.
#[derive(Debug, Clone, Serialize)]
@ -41,11 +42,14 @@ pub fn prepare(task: &TaskSpec, kit_root: &Path) -> Result<AgentInvocation> {
if task.task.role.is_empty() {
return Err(anyhow!("task.role is empty"));
}
if task.task.agent_id.is_empty() {
return Err(anyhow!(
"task.agent-id is empty — orchestrator must allocate via kei-ledger"
));
}
// Auto-generate agent-id if absent. Format: `ag-<role-slug>-<unix-ms-hex>-<4hex-rand>`
// Orchestrator can still pre-allocate via `kei-ledger fork` and write explicit
// agent-id into task.toml for deterministic id; auto-gen is the ergonomic default.
let agent_id = if task.task.agent_id.is_empty() {
autogen_agent_id(&task.task.role)
} else {
task.task.agent_id.clone()
};
let role_file = load_role_meta(kit_root, &task.task.role)?;
if !role_file.role.spawnable {
return Err(anyhow!(
@ -62,12 +66,16 @@ pub fn prepare(task: &TaskSpec, kit_root: &Path) -> Result<AgentInvocation> {
.clone()
.unwrap_or_else(|| default_subagent_type(&task.task.role));
let isolation = default_isolation(&task.task.role);
let description = build_description(&task.task.role, &task.task.agent_id);
let verify_command = build_verify_command(&task.task.agent_id);
let ledger_row = build_ledger_row(task);
let dna = Dna::compose(task, &resolved).render();
let description = build_description(&task.task.role, &agent_id);
let verify_command = build_verify_command(&agent_id);
let ledger_row = build_ledger_row_with_id(task, &agent_id);
// DNA uses effective agent-id; if auto-generated we inject it into a clone
// of TaskSpec so Dna::compose sees the resolved id.
let mut task_for_dna = task.clone();
task_for_dna.task.agent_id = agent_id.clone();
let dna = Dna::compose(&task_for_dna, &resolved).render();
Ok(AgentInvocation {
agent_id: task.task.agent_id.clone(),
agent_id,
role: task.task.role.clone(),
prompt,
subagent_type,
@ -79,6 +87,15 @@ pub fn prepare(task: &TaskSpec, kit_root: &Path) -> Result<AgentInvocation> {
})
}
fn autogen_agent_id(role: &str) -> String {
let ts_ms = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_millis())
.unwrap_or(0);
let rand_hex = format!("{:04x}", rand::random::<u16>());
format!("ag-{}-{:x}-{}", role, ts_ms, rand_hex)
}
/// Human-readable block — copy into Claude Code's Agent-tool dialog.
pub fn render_human(inv: &AgentInvocation) -> String {
let iso = inv.isolation.as_deref().unwrap_or("<none>");
@ -141,6 +158,10 @@ fn build_verify_command(agent_id: &str) -> String {
}
fn build_ledger_row(task: &TaskSpec) -> String {
build_ledger_row_with_id(task, &task.task.agent_id)
}
fn build_ledger_row_with_id(task: &TaskSpec, agent_id: &str) -> String {
let parent = task
.task
.parent_agent
@ -149,7 +170,7 @@ fn build_ledger_row(task: &TaskSpec) -> String {
.unwrap_or("none");
format!(
"running agent-id={} role={} parent={}",
task.task.agent_id, task.task.role, parent
agent_id, task.task.role, parent
)
}