From 9c8c7c359873024a0953ca587d485f2b9e45da7b Mon Sep 17 00:00:00 2001 From: Parfii-bot Date: Thu, 23 Apr 2026 05:23:55 +0800 Subject: [PATCH] fix(prepare): auto-generate agent-id when absent + dogfood ergonomics MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Dogfood gap found: orchestrator running `kei-agent-runtime prepare ` hit "task.agent-id is empty" error. Required 3-step flow (ledger fork → edit task.toml → prepare) for every spawn. Fix: auto-generate `ag---<4hex-rand>` if task.agent-id empty. Orchestrator can still pre-allocate for deterministic ids; auto-gen is ergonomic default. DNA compose sees the effective id via clone+inject into TaskSpec before Dna::compose. Helper `autogen_agent_id()` uses SystemTime millis + rand u16 — low collision at orchestrator spawn rate (< 1 spawn/ms). build_ledger_row() split: original preserved as thin wrapper, new build_ledger_row_with_id() accepts explicit id for auto-gen path. Verified end-to-end via `/tmp/keisei-dogfood/test-task.toml`: prepare now emits complete AgentInvocation including composed prompt, DNA, verify command — ready for orchestrator Agent-tool invocation without pre-alloc step. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../_rust/kei-agent-runtime/src/prepare.rs | 43 ++++++++++++++----- 1 file changed, 32 insertions(+), 11 deletions(-) diff --git a/_primitives/_rust/kei-agent-runtime/src/prepare.rs b/_primitives/_rust/kei-agent-runtime/src/prepare.rs index 75bbf6e..8c7fe1f 100644 --- a/_primitives/_rust/kei-agent-runtime/src/prepare.rs +++ b/_primitives/_rust/kei-agent-runtime/src/prepare.rs @@ -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 { 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---<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 { .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 { }) } +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::()); + 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(""); @@ -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 ) }