diff --git a/crates/rally-workflow-build/src/lib.rs b/crates/rally-workflow-build/src/lib.rs index dfe3cb1..4ba61dd 100644 --- a/crates/rally-workflow-build/src/lib.rs +++ b/crates/rally-workflow-build/src/lib.rs @@ -87,7 +87,7 @@ fn default_state_version() -> u32 { } fn default_implementer() -> String { - "implementer".to_string() + "implement".to_string() } impl BuildWorkflowState { @@ -580,7 +580,7 @@ pub fn process_review_state(state: &mut SessionState) -> Result> pub fn is_implementer(state: &SessionState, agent: &str) -> bool { match read_workflow_state(state) { Ok(workflow_state) => is_implementer_with_state(&workflow_state, agent), - Err(_) => agent == "implementer", + Err(_) => agent == "implement", } } diff --git a/prompts/skill-wrapper.md b/prompts/skill-wrapper.md index 0425c1e..b54e596 100644 --- a/prompts/skill-wrapper.md +++ b/prompts/skill-wrapper.md @@ -6,10 +6,71 @@ rally-managed: true # Rally -Run this command first with the user's arguments: +Rally coordinates multi-agent coding sessions. Your job is to figure out what the user wants, construct the right `{{RALLY_BIN}} skill run` command, execute it, and follow the instruction loop. + +## Step 1: Determine the command + +The user said: `$ARGUMENTS` + +### If the arguments clearly match a known command syntax + +Run it directly. Examples of clear, structured arguments: + +| User says | Run | +|-----------|-----| +| `implement todos/foo.md implement` | `{{RALLY_BIN}} skill run implement todos/foo.md implement` | +| `implement todos/foo.md review --reviewers 2` | `{{RALLY_BIN}} skill run implement todos/foo.md review --reviewers 2` | +| `negotiate "auth design" agent-alpha --agents 3` | `{{RALLY_BIN}} skill run negotiate "auth design" agent-alpha --agents 3` | +| (empty / no arguments) | `{{RALLY_BIN}} skill run` (resumes last session) | + +### If the arguments are natural language or ambiguous + +Interpret the user's intent to construct a `{{RALLY_BIN}} skill run` command. Use these rules: + +**Available workflows:** + +- `{{RALLY_BIN}} skill run implement [--reviewers N]` — implement a todo with code review +- `{{RALLY_BIN}} skill run negotiate [--agents N]` — multi-agent planning/negotiation + +**Roles for implement sessions:** + +- `implement` — writes the code (use when the user says "implement", "build", "code", etc.) +- `review` — reviews checkpoints (use when the user says "review", "check", etc.) + +**Rally auto-assigns reviewer names.** Always pass `review` as the role — rally will automatically assign unique names (review-1, review-2, etc.). Never pick your own reviewer number. + +**Mapping examples:** + +| User says | Interpretation | +|-----------|---------------| +| `impl todos/foo.md 2 reviewers` | `{{RALLY_BIN}} skill run implement todos/foo.md implement --reviewers 2` | +| `impl todos/foo.md you implement with 2 reviewers` | `{{RALLY_BIN}} skill run implement todos/foo.md implement --reviewers 2` | +| `review todos/foo.md` | `{{RALLY_BIN}} skill run implement todos/foo.md review` | +| `implement the microvm todo as reviewer` | Find the todo file, run as review | +| `impl todos/foo.md` | If role is ambiguous, ask the user | + +### If you need more workspace context + +Run this to discover todo files, active sessions, and saved context: ```bash -{{RALLY_BIN}} skill agent-howto $ARGUMENTS +{{RALLY_BIN}} skill context ``` -Then follow the printed instructions exactly. +## Step 2: Run the command + +Run the `{{RALLY_BIN}} skill run ...` command you determined above. + +## Step 3: Follow the instruction loop + +1. **READ** the instruction printed by the command. +2. **DO** exactly what it says — write files, run tests, git commit, etc. +3. **RUN** the follow-up commands listed at the end of your instruction (checkpoint, review, etc). Run them ALL, in order. +4. **RUN** `{{RALLY_BIN}} skill run` again with no arguments to get your next instruction. +5. **REPEAT** from step 1. Do NOT stop until the output says "session complete; stop working". + +**IMPORTANT: You are ONE agent in a multi-agent session. Other agents are running in separate terminals.** + +- If the command prints "Waiting for ..." — just wait and poll. Do NOT try to spawn or recruit other agents. +- If the command exits with code 2 or prints "session complete" — you are done. Stop. +- Rally tells you your assigned name (e.g. "registered 'review-2'"). Use that name for all subsequent commands. diff --git a/src/cli.rs b/src/cli.rs index 65e4fbc..4d6b0eb 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -62,6 +62,8 @@ pub enum Command { Status(StatusArgs), #[command(about = "List all sessions")] Sessions, + #[command(about = "Interactively select and delete a session")] + Reset, #[command(about = "File a divergence issue (plan sessions)")] FileIssue(FileIssueArgs), #[command(about = "Challenge another agent's position (plan sessions)")] @@ -114,7 +116,7 @@ pub struct CreateArgs { pub workflow: Option, #[arg( long, - default_value = "implementer", + default_value = "implement", help = "Implementer agent name for implement sessions" )] pub implementer: String, @@ -346,6 +348,11 @@ pub enum SkillSubcommand { long_about = "Print the latest Rally agent instructions for `/rally` wrappers.\n\nPass the same wrapper args so this command can print the exact follow-up `rally skill run ...` command to execute." )] AgentHowto(SkillAgentHowtoArgs), + #[command( + about = "Print workspace context for intelligent command resolution", + long_about = "Print workspace context for intelligent command resolution.\n\nOutputs available workflows, todo files in the workspace, active sessions, and saved session context. Designed to be read by a coding agent to determine the right `rally skill run` command." + )] + Context(SkillContextArgs), #[command(about = "Validate wrapper installation and target paths")] Doctor(SkillDoctorArgs), #[command(about = "Uninstall Rally-managed wrapper artifacts")] @@ -399,6 +406,15 @@ pub struct SkillAgentHowtoArgs { pub args: Vec, } +#[derive(Args, Debug)] +pub struct SkillContextArgs { + #[arg( + long, + help = "Workspace directory to scan for todo files (defaults to current directory)" + )] + pub workspace: Option, +} + #[derive(Args, Debug)] pub struct SkillDoctorArgs { #[arg( diff --git a/src/command_surface.rs b/src/command_surface.rs index f1d0270..1ccbafc 100644 --- a/src/command_surface.rs +++ b/src/command_surface.rs @@ -11,8 +11,8 @@ use serde::{Deserialize, Serialize}; use crate::{ WorkflowRegistry, cli::{ - JoinArgs, SkillAgentHowtoArgs, SkillDoctorArgs, SkillInstallArgs, SkillRunArgs, - SkillTargetArg, SkillUninstallArgs, + JoinArgs, SkillAgentHowtoArgs, SkillContextArgs, SkillDoctorArgs, SkillInstallArgs, + SkillRunArgs, SkillTargetArg, SkillUninstallArgs, }, commands, state::SessionHandle, @@ -82,10 +82,11 @@ pub fn run(args: &SkillRunArgs, registry: &WorkflowRegistry) -> Result { create_args, } => { let session_name = create_or_join(&create_args, registry)?; - ensure_agent_registered(&session_name, &agent_name, registry)?; + let actual_name = + ensure_agent_registered(&session_name, &agent_name, registry)?; let code = watch::next_with_registry( &session_name, - &agent_name, + &actual_name, Some(DEFAULT_RUN_TIMEOUT_SECS), registry, )?; @@ -101,7 +102,7 @@ pub fn run(args: &SkillRunArgs, registry: &WorkflowRegistry) -> Result { workspace, SavedRunContext { session: session_name, - agent: agent_name, + agent: actual_name, workflow: Some(cmd.to_string()), }, ); @@ -141,10 +142,10 @@ pub fn run(args: &SkillRunArgs, registry: &WorkflowRegistry) -> Result { &mut prompter, )?; - ensure_agent_registered(&resolved.session, &resolved.agent, registry)?; + let actual_name = ensure_agent_registered(&resolved.session, &resolved.agent, registry)?; let code = watch::next_with_registry( &resolved.session, - &resolved.agent, + &actual_name, Some(DEFAULT_RUN_TIMEOUT_SECS), registry, )?; @@ -160,7 +161,7 @@ pub fn run(args: &SkillRunArgs, registry: &WorkflowRegistry) -> Result { workspace, SavedRunContext { session: resolved.session, - agent: resolved.agent, + agent: actual_name, workflow: resolved.workflow, }, ); @@ -190,6 +191,121 @@ pub fn agent_howto(args: &SkillAgentHowtoArgs) -> Result<()> { Ok(()) } +pub fn context(args: &SkillContextArgs, registry: &WorkflowRegistry) -> Result<()> { + let workspace = match &args.workspace { + Some(p) => p.clone(), + None => env::current_dir().context("resolving current working directory")?, + }; + + // Available workflow commands + println!("## Available Commands\n"); + let commands = registry.list_commands(); + if commands.is_empty() { + println!("(none)\n"); + } else { + for workflow in &commands { + if let Some(cmd) = workflow.run_command() { + println!( + "- `rally skill run {}`\n {}", + cmd.usage(), + cmd.description() + ); + } + } + println!(); + } + + // Roles + println!("## Roles\n"); + println!("- implement sessions: `implement` (writes code), `review` (reviews checkpoints)"); + println!("- negotiate sessions: any agent name (e.g. `agent-alpha`, `agent-beta`)"); + println!(); + + // Scan for todo files + println!("## Todo Files (in {})\n", workspace.display()); + let mut found_todos = Vec::new(); + for dir_name in &["todos", "todo"] { + let dir = workspace.join(dir_name); + if dir.is_dir() { + if let Ok(entries) = fs::read_dir(&dir) { + for entry in entries.flatten() { + let path = entry.path(); + if path.extension().is_some_and(|ext| ext == "md") { + let rel = path + .strip_prefix(&workspace) + .unwrap_or(&path) + .display() + .to_string(); + found_todos.push(rel); + } + } + } + } + } + found_todos.sort(); + if found_todos.is_empty() { + println!("(none found)\n"); + } else { + for todo in &found_todos { + println!("- {todo}"); + } + println!(); + } + + // Active sessions for this workspace + println!("## Active Sessions\n"); + let workspace_str = workspace.display().to_string(); + let sessions = crate::state::list_sessions().unwrap_or_default(); + let mut found_sessions = false; + for session_name in &sessions { + if let Ok(handle) = SessionHandle::open(session_name) { + if let Ok(state) = handle.load_state() { + if state.workspace.as_deref() == Some(&workspace_str) { + let agents: Vec<_> = state.agents.keys().cloned().collect(); + println!( + "- **{}** (type: {:?}, phase: {:?}, agents: {}/{}, registered: [{}])", + state.name, + state.session_type, + state.phase, + state.agents.len(), + state.config.expected_agents, + agents.join(", ") + ); + found_sessions = true; + } + } + } + } + if !found_sessions { + println!("(none for this workspace)\n"); + } else { + println!(); + } + + // Saved context + println!("## Saved Context\n"); + let store_path = context_store_path()?; + let legacy_store_path = legacy_context_store_path()?; + let store = load_context_store(&store_path, Some(&legacy_store_path)); + let workspace_key = workspace + .canonicalize() + .unwrap_or_else(|_| workspace.clone()) + .display() + .to_string(); + if let Some(saved) = store.by_workspace.get(&workspace_key) { + println!("- Last session: {}", saved.session); + println!("- Last role: {}", saved.agent); + if let Some(wf) = &saved.workflow { + println!("- Workflow: {wf}"); + } + } else { + println!("(no saved context for this workspace)"); + } + println!(); + + Ok(()) +} + pub fn doctor(args: &SkillDoctorArgs, registry: &WorkflowRegistry) -> Result<()> { let env = AdapterEnvironment::detect()?; let render_ctx = WrapperRenderContext::default(); @@ -870,8 +986,9 @@ fn parse_invite_token(raw: &str) -> Option { pub(crate) fn normalize_agent_selector(raw: &str) -> String { let lowered = raw.trim().to_ascii_lowercase(); match lowered.as_str() { - "implement" | "implementer" => "implementer".to_string(), - "review" | "reviewer" | "reviewer1" | "reviewer-1" => "reviewer-1".to_string(), + "implement" => "implement".to_string(), + "review" => "review".to_string(), + "review1" | "review-1" => "review-1".to_string(), _ => raw.trim().to_string(), } } @@ -977,19 +1094,29 @@ fn create_or_join( } } -fn ensure_agent_registered(session: &str, agent: &str, registry: &WorkflowRegistry) -> Result<()> { - let known = match SessionHandle::open(session) { - Ok(handle) => { - let state = handle.load_state()?; - state.agents.contains_key(agent) - } - Err(err) => { - return Err(err) - .with_context(|| format!("resolving session '{}' before join", session)); +fn ensure_agent_registered( + session: &str, + agent: &str, + registry: &WorkflowRegistry, +) -> Result { + // "review" is the auto-assign base name — always go through join so the + // auto-numbering logic in join_with_registry can assign review-1, review-2, etc. + let needs_auto_assign = agent == "review"; + + if !needs_auto_assign { + let known = match SessionHandle::open(session) { + Ok(handle) => { + let state = handle.load_state()?; + state.agents.contains_key(agent) + } + Err(err) => { + return Err(err) + .with_context(|| format!("resolving session '{}' before join", session)); + } + }; + if known { + return Ok(agent.to_string()); } - }; - if known { - return Ok(()); } commands::join_with_registry( @@ -1127,7 +1254,8 @@ mod tests { fn wrapper_rendering_delegates_to_command_run() { let adapter = DotDirAdapter::new(HarnessTarget::Droid); let rendered = adapter.render_wrapper(&WrapperRenderContext::default()); - assert!(rendered.contains("rally skill agent-howto $ARGUMENTS")); + assert!(rendered.contains("rally skill run")); + assert!(rendered.contains("rally skill context")); assert!(rendered.contains("rally-managed: true")); assert!(rendered.starts_with("---\nname: rally\n")); } @@ -1203,7 +1331,7 @@ mod tests { let skill = root.join("skills").join("rally").join("SKILL.md"); let content = fs::read_to_string(&skill)?; - assert!(content.contains("rally skill agent-howto $ARGUMENTS")); + assert!(content.contains("rally skill run")); assert!(content.contains("rally-managed: true")); let second = install_with_environment( @@ -1457,7 +1585,7 @@ mod tests { let resolved = resolve_run_context( &args, Some("plan".to_string()), - Some("reviewer".to_string()), + Some("review".to_string()), Some(invite), Some(&saved), &mut prompter, @@ -1481,13 +1609,13 @@ mod tests { }; let invite = InviteContext { session: "token-session".to_string(), - agent: "reviewer-1".to_string(), + agent: "review-1".to_string(), }; let mut prompter = StaticPrompter::new(&[]); let resolved = resolve_run_context(&args, None, None, Some(invite), Some(&saved), &mut prompter)?; assert_eq!(resolved.session, "token-session"); - assert_eq!(resolved.agent, "reviewer-1"); + assert_eq!(resolved.agent, "review-1"); Ok(()) } @@ -1516,7 +1644,7 @@ mod tests { let mut prompter = StaticPrompter::new(&["prompt-session", "review"]); let resolved = resolve_run_context(&args, None, None, None, None, &mut prompter)?; assert_eq!(resolved.session, "prompt-session"); - assert_eq!(resolved.agent, "reviewer-1"); + assert_eq!(resolved.agent, "review"); Ok(()) } diff --git a/src/commands.rs b/src/commands.rs index 3f554ef..2a3d01a 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -172,7 +172,7 @@ pub fn create_with_registry(args: &CreateArgs, registry: &WorkflowRegistry) -> R Ok(session_name) } -pub fn join_with_registry(args: &JoinArgs, registry: &WorkflowRegistry) -> Result<()> { +pub fn join_with_registry(args: &JoinArgs, registry: &WorkflowRegistry) -> Result { let wait_start = std::time::Instant::now(); let state_path = session_dir(&args.session).join("state.json"); let mut announced_wait = false; @@ -192,16 +192,31 @@ pub fn join_with_registry(args: &JoinArgs, registry: &WorkflowRegistry) -> Resul let handle = SessionHandle::open(&args.session)?; let mut state = handle.load_state()?; + // Auto-number: if agent name is the base "review", assign next available review-N + let actual_agent = if args.agent == "review" { + let mut name = None; + for n in 1..=state.config.expected_agents as usize { + let candidate = format!("review-{n}"); + if !state.agents.contains_key(&candidate) { + name = Some(candidate); + break; + } + } + name.unwrap_or_else(|| args.agent.clone()) + } else { + args.agent.clone() + }; + let now_ts = now(); state .agents - .entry(args.agent.to_string()) + .entry(actual_agent.clone()) .and_modify(|a| { a.last_seen = now_ts; }) .or_insert(AgentState { - name: args.agent.to_string(), + name: actual_agent.clone(), joined_at: now_ts, last_seen: now_ts, phase_status: "joined".to_string(), @@ -209,16 +224,16 @@ pub fn join_with_registry(args: &JoinArgs, registry: &WorkflowRegistry) -> Resul }); if state.session_type == SessionType::Implement { - let role_status = if implement::is_implementer(&state, &args.agent) { - "implementer" + let role_status = if implement::is_implementer(&state, &actual_agent) { + "implement" } else { - "reviewer" + "review" }; - if let Some(agent_state) = state.agents.get_mut(&args.agent) { + if let Some(agent_state) = state.agents.get_mut(&actual_agent) { agent_state.phase_status = role_status.to_string(); } } - dispatch_join_hook(&mut state, &handle.session_dir, &args.agent, registry)?; + dispatch_join_hook(&mut state, &handle.session_dir, &actual_agent, registry)?; if state.phase == SessionPhase::Registration && state.agents.len() as u32 >= state.config.expected_agents @@ -233,9 +248,9 @@ pub fn join_with_registry(args: &JoinArgs, registry: &WorkflowRegistry) -> Resul handle.save_state(&state)?; println!( "registered '{}' in session '{}' (phase: {:?})", - args.agent, args.session, state.phase + actual_agent, args.session, state.phase ); - Ok(()) + Ok(actual_agent) } pub fn next_with_registry(args: &NextArgs, registry: &WorkflowRegistry) -> Result { @@ -461,7 +476,7 @@ pub fn chain_with_registry(args: &ChainArgs, registry: &WorkflowRegistry) -> Res todo: Some(todo), workspace: Some(workspace), workflow: None, - implementer: "implementer".to_string(), + implementer: "implement".to_string(), topic: None, max_rounds: 4, turn_timeout_secs: 300, @@ -490,6 +505,84 @@ pub fn sessions() -> Result<()> { Ok(()) } +pub fn reset() -> Result<()> { + let list = list_sessions()?; + if list.is_empty() { + println!("no sessions to reset"); + return Ok(()); + } + + // Build display lines sorted by most recent (newest first). + let mut entries: Vec<(String, String, String)> = Vec::new(); + for name in &list { + let handle = match SessionHandle::open(name) { + Ok(h) => h, + Err(_) => { + entries.push((name.clone(), String::new(), format!("{:<40} (corrupt)", name))); + continue; + } + }; + let state = match handle.load_state() { + Ok(s) => s, + Err(_) => { + entries.push((name.clone(), String::new(), format!("{:<40} (corrupt)", name))); + continue; + } + }; + let most_recent = state + .agents + .values() + .map(|a| a.last_seen) + .max() + .unwrap_or(state.created_at); + let agent_count = state.agents.len(); + let line = format!( + "{:<40} {:>2} agents {:?} {}", + name, agent_count, state.phase, most_recent.format("%Y-%m-%d %H:%M") + ); + entries.push((name.clone(), most_recent.to_rfc3339(), line)); + } + entries.sort_by(|a, b| b.1.cmp(&a.1)); + + let fzf_input = entries + .iter() + .map(|(_, _, line)| line.as_str()) + .collect::>() + .join("\n"); + + let mut child = ProcessCommand::new("fzf") + .args(["--multi", "--header", "Select session(s) to delete (TAB to multi-select)"]) + .stdin(std::process::Stdio::piped()) + .stdout(std::process::Stdio::piped()) + .spawn() + .context("failed to launch fzf — is it installed?")?; + + { + use std::io::Write; + let stdin = child.stdin.as_mut().expect("fzf stdin"); + stdin.write_all(fzf_input.as_bytes())?; + } + + let output = child.wait_with_output()?; + if !output.status.success() { + return Ok(()); + } + + let selected = String::from_utf8_lossy(&output.stdout); + for line in selected.lines() { + let session_name = line.split_whitespace().next().unwrap_or("").trim(); + if session_name.is_empty() { + continue; + } + let dir = session_dir(session_name); + if dir.exists() { + fs::remove_dir_all(&dir)?; + println!("deleted {session_name}"); + } + } + Ok(()) +} + pub fn status_with_registry(args: &StatusArgs, registry: &WorkflowRegistry) -> Result<()> { let handle = SessionHandle::open(&args.session)?; let state = handle.load_state()?; diff --git a/src/lib.rs b/src/lib.rs index 1f83b8e..82b72a6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -39,6 +39,10 @@ pub fn run_cli_with_registry(cli: Cli, registry: &WorkflowRegistry) -> Result { + commands::reset()?; + Ok(0) + } Command::FileIssue(args) => { commands::file_issue_with_registry(&args, registry)?; Ok(0) @@ -111,6 +115,10 @@ pub fn run_cli_with_registry(cli: Cli, registry: &WorkflowRegistry) -> Result { + command_surface::context(&inner, registry)?; + 0 + } SkillSubcommand::Doctor(inner) => { command_surface::doctor(&inner, registry)?; 0 diff --git a/src/workflow/builtin.rs b/src/workflow/builtin.rs index bfc4647..2092ec5 100644 --- a/src/workflow/builtin.rs +++ b/src/workflow/builtin.rs @@ -110,7 +110,7 @@ impl RunCommand for ImplementRunCommand { todo: Some(todo_path), workspace: Some(workspace.to_path_buf()), workflow: None, - implementer: "implementer".to_string(), + implementer: "implement".to_string(), topic: None, max_rounds: 4, turn_timeout_secs: 300, @@ -676,7 +676,7 @@ mod tests { let (session_name, agent_name, create_args) = unwrap_session(cmd.resolve(&args, &workspace).expect("resolve")); assert_eq!(session_name, "implement-create-todo"); - assert_eq!(agent_name, "implementer"); + assert_eq!(agent_name, "implement"); assert_eq!(create_args.name, "implement-create-todo"); assert_eq!(create_args.reviewers, Some(1)); assert_eq!( @@ -694,7 +694,7 @@ mod tests { let args = vec!["my-task.md".to_string(), "review".to_string()]; let (session_name, agent_name, _) = unwrap_session(cmd.resolve(&args, &workspace).expect("resolve")); - assert_eq!(agent_name, "reviewer-1"); + assert_eq!(agent_name, "review"); assert_eq!(session_name, "implement-my-task"); } @@ -771,7 +771,7 @@ mod tests { let (session_name, agent_name, create_args) = unwrap_session(cmd.resolve(&args, &workspace).expect("resolve with absolute path")); assert_eq!(session_name, "implement-abs-task"); - assert_eq!(agent_name, "implementer"); + assert_eq!(agent_name, "implement"); assert!(create_args.todo.as_ref().unwrap().exists()); } diff --git a/src/workflow/interop.rs b/src/workflow/interop.rs index 1634d34..45eb10e 100644 --- a/src/workflow/interop.rs +++ b/src/workflow/interop.rs @@ -344,9 +344,9 @@ mod tests { let mut agents = BTreeMap::new(); let ts = now(); agents.insert( - "implementer".to_string(), + "implement".to_string(), AgentState { - name: "implementer".to_string(), + name: "implement".to_string(), joined_at: ts, last_seen: ts, phase_status: "joined".to_string(), @@ -390,7 +390,7 @@ mod tests { workflow_id: "child", namespace: "child_a", }, - "implementer", + "implement", "ack", &args, )?; @@ -425,7 +425,7 @@ mod tests { workflow_id: "child", namespace: "child_b", }, - "implementer", + "implement", "fail", &args, ) diff --git a/src/workflow/mod.rs b/src/workflow/mod.rs index 2be3c7e..d8b71dc 100644 --- a/src/workflow/mod.rs +++ b/src/workflow/mod.rs @@ -515,14 +515,14 @@ mod tests { session_dir, state: &mut state, }, - agent: "implementer", + agent: "implement", }; dispatch_join(&workflow, &mut ctx).expect("join dispatch") }; assert_eq!( join, WorkflowDispatch::Join(JoinDispatch { - message: Some("joined implementer".to_string()) + message: Some("joined implement".to_string()) }) ); @@ -532,14 +532,14 @@ mod tests { session_dir, state: &mut state, }, - agent: "implementer", + agent: "implement", }; dispatch_next_poll(&workflow, &mut ctx).expect("next dispatch") }; assert_eq!( next_poll, WorkflowDispatch::NextPoll(NextPollDispatch::Instruction { - body: "next for implementer".to_string(), + body: "next for implement".to_string(), }) ); @@ -549,7 +549,7 @@ mod tests { session_dir, state: &mut state, }, - agent: "implementer", + agent: "implement", }; dispatch_done(&workflow, &mut ctx).expect("done dispatch") }; @@ -568,7 +568,7 @@ mod tests { session_dir, state: &mut state, }, - agent: "implementer", + agent: "implement", name: "checkpoint", args: &action_args, }; @@ -587,7 +587,7 @@ mod tests { session_dir, state: &state, }, - agent: Some("implementer"), + agent: Some("implement"), }; dispatch_status(&workflow, &ctx).expect("status dispatch") }; @@ -624,7 +624,7 @@ mod tests { session_dir, state: &mut state, }, - agent: "implementer", + agent: "implement", name: "unknown", args: &args, }; diff --git a/tests/command_install_run.rs b/tests/command_install_run.rs index 4f5d7bb..8c06049 100644 --- a/tests/command_install_run.rs +++ b/tests/command_install_run.rs @@ -134,7 +134,7 @@ fn command_install_run_entrypoint_integration() -> Result<()> { _ => root.join("skills").join("rally").join("SKILL.md"), }; let content = fs::read_to_string(&skill)?; - assert!(content.contains("rally skill agent-howto $ARGUMENTS")); + assert!(content.contains("rally skill run")); assert!(content.contains("rally-managed: true")); } @@ -215,7 +215,7 @@ fn command_install_run_entrypoint_integration() -> Result<()> { "--session", &session_one, "--as", - "implementer", + "implement", ], ®istry, )?; @@ -242,7 +242,7 @@ fn command_install_run_entrypoint_integration() -> Result<()> { "--session", &session_two, "--as", - "implementer", + "implement", ], ®istry, )?; @@ -257,16 +257,16 @@ fn command_install_run_entrypoint_integration() -> Result<()> { fs::write( &context_store, format!( - "{{\"by_workspace\":{{\"{workspace_key}\":{{\"session\":\"{session_one}\",\"agent\":\"implementer\"}}}}}}" + "{{\"by_workspace\":{{\"{workspace_key}\":{{\"session\":\"{session_one}\",\"agent\":\"implement\"}}}}}}" ), )?; - let session_one_seen_before = read_agent_last_seen(&temp_home, &session_one, "implementer")?; + let session_one_seen_before = read_agent_last_seen(&temp_home, &session_one, "implement")?; assert_eq!( run_cli(&["rally", "skill", "run", "--non-interactive"], ®istry,)?, 0 ); - let session_one_seen_after = read_agent_last_seen(&temp_home, &session_one, "implementer")?; + let session_one_seen_after = read_agent_last_seen(&temp_home, &session_one, "implement")?; assert_ne!(session_one_seen_before, session_one_seen_after); assert_eq!( @@ -275,7 +275,7 @@ fn command_install_run_entrypoint_integration() -> Result<()> { "rally", "skill", "run", - &format!("rly:{session_two}:implementer"), + &format!("rly:{session_two}:implement"), "--non-interactive", ], ®istry, @@ -296,9 +296,9 @@ fn command_install_run_entrypoint_integration() -> Result<()> { "--session", &session_one, "--as", - "implementer", + "implement", "--invite", - &format!("rly:{session_two}:implementer"), + &format!("rly:{session_two}:implement"), "--non-interactive", ], ®istry, @@ -320,7 +320,7 @@ fn command_install_run_entrypoint_integration() -> Result<()> { "--session", &session_one, "--as", - "implementer", + "implement", "--non-interactive", ], ®istry, @@ -371,7 +371,7 @@ fn command_install_run_entrypoint_integration() -> Result<()> { "--session", &session_two, "--as", - "implementer", + "implement", "--non-interactive", ], ®istry, @@ -394,7 +394,7 @@ fn command_install_run_entrypoint_integration() -> Result<()> { "--session", &session_one, "--as", - "implementer", + "implement", "--non-interactive", ], ®istry, @@ -410,7 +410,7 @@ fn command_install_run_entrypoint_integration() -> Result<()> { let run_cmd_todo_rel = format!("{run_cmd_todo_name}.md"); let run_cmd_session = format!("implement-{run_cmd_todo_name}"); - // Use --reviewers 0 so the single implementer transitions the session to Implement + // Use --reviewers 0 so the single implement agent transitions the session to Implement let run_cmd_result = run_cli( &[ "rally", @@ -427,13 +427,13 @@ fn command_install_run_entrypoint_integration() -> Result<()> { )?; assert_eq!( run_cmd_result, 0, - "first agent (implementer) via run command should get instruction" + "first agent (implement) via run command should get instruction" ); { let handle = SessionHandle::open(&run_cmd_session)?; let state = handle.load_state()?; - assert!(state.agents.contains_key("implementer")); + assert!(state.agents.contains_key("implement")); assert_eq!(state.phase, SessionPhase::Implement); } @@ -473,7 +473,7 @@ fn command_install_run_entrypoint_integration() -> Result<()> { { let handle = SessionHandle::open(&run_cmd_session)?; let state = handle.load_state()?; - assert!(state.agents.contains_key("reviewer-1")); + assert!(state.agents.contains_key("review-1")); } if run_cmd_todo.exists() { diff --git a/tests/e2e_matrix.py b/tests/e2e_matrix.py index f54b320..428f61c 100644 --- a/tests/e2e_matrix.py +++ b/tests/e2e_matrix.py @@ -147,7 +147,7 @@ def run_solo_test(agent_name, timeout=120): """)) start = time.time() - prompt = build_prompt(agent_name, "implementer", todo_file.name, "--reviewers 0") + prompt = build_prompt(agent_name, "implement", todo_file.name, "--reviewers 0") logfile = os.path.join(logdir, "agent.log") proc = launch_agent(agent_name, prompt, logfile) @@ -237,8 +237,8 @@ def run_pair_test(impl_agent, revw_agent, timeout=300): """)) start = time.time() - impl_prompt = build_prompt(impl_agent, "implementer", todo_file.name) - revw_prompt = build_prompt(revw_agent, "reviewer", todo_file.name) + impl_prompt = build_prompt(impl_agent, "implement", todo_file.name) + revw_prompt = build_prompt(revw_agent, "review", todo_file.name) impl_log = os.path.join(logdir, "impl.log") revw_log = os.path.join(logdir, "revw.log") @@ -288,10 +288,10 @@ def run_pair_test(impl_agent, revw_agent, timeout=300): errors = [] if state is None: errors.append("session never created") - elif "implementer" not in agents_joined: - errors.append("implementer never joined") - elif "reviewer-1" not in agents_joined: - errors.append("reviewer never joined") + elif "implement" not in agents_joined: + errors.append("implement agent never joined") + elif "review-1" not in agents_joined: + errors.append("review agent never joined") if final_phase != "done": errors.append(f"phase={final_phase}, expected done") if file_content is None: diff --git a/tests/extensibility_mvp.rs b/tests/extensibility_mvp.rs index 348fcd1..a1c958f 100644 --- a/tests/extensibility_mvp.rs +++ b/tests/extensibility_mvp.rs @@ -94,7 +94,7 @@ fn regression_plan_and_build_happy_paths_with_namespaced_commands() -> Result<() commands::join_with_registry( &JoinArgs { session: build_session.clone(), - agent: "implementer".to_string(), + agent: "implement".to_string(), timeout: None, }, ®istry, @@ -102,7 +102,7 @@ fn regression_plan_and_build_happy_paths_with_namespaced_commands() -> Result<() commands::join_with_registry( &JoinArgs { session: build_session.clone(), - agent: "reviewer-1".to_string(), + agent: "review-1".to_string(), timeout: None, }, ®istry, @@ -111,7 +111,7 @@ fn regression_plan_and_build_happy_paths_with_namespaced_commands() -> Result<() commands::next_with_registry( &NextArgs { session: build_session.clone(), - agent: "implementer".to_string(), + agent: "implement".to_string(), timeout: Some(2), }, ®istry @@ -126,7 +126,7 @@ fn regression_plan_and_build_happy_paths_with_namespaced_commands() -> Result<() "--session", &build_session, "--as", - "implementer", + "implement", ], ®istry, )?; @@ -134,7 +134,7 @@ fn regression_plan_and_build_happy_paths_with_namespaced_commands() -> Result<() commands::next_with_registry( &NextArgs { session: build_session.clone(), - agent: "reviewer-1".to_string(), + agent: "review-1".to_string(), timeout: Some(2), }, ®istry @@ -149,7 +149,7 @@ fn regression_plan_and_build_happy_paths_with_namespaced_commands() -> Result<() "--session", &build_session, "--as", - "reviewer-1", + "review-1", "--verdict", "approve", ], @@ -159,7 +159,7 @@ fn regression_plan_and_build_happy_paths_with_namespaced_commands() -> Result<() commands::next_with_registry( &NextArgs { session: build_session.clone(), - agent: "reviewer-1".to_string(), + agent: "review-1".to_string(), timeout: Some(2), }, ®istry @@ -174,7 +174,7 @@ fn regression_plan_and_build_happy_paths_with_namespaced_commands() -> Result<() "--session", &build_session, "--as", - "reviewer-1", + "review-1", "--verdict", "approve", "--message", @@ -186,7 +186,7 @@ fn regression_plan_and_build_happy_paths_with_namespaced_commands() -> Result<() commands::next_with_registry( &NextArgs { session: build_session.clone(), - agent: "implementer".to_string(), + agent: "implement".to_string(), timeout: Some(2), }, ®istry @@ -2004,7 +2004,7 @@ fn workflow_errors_do_not_partially_write_state() -> Result<()> { todo: None, workspace: None, workflow: Some("error".to_string()), - implementer: "implementer".to_string(), + implementer: "implement".to_string(), topic: None, max_rounds: 4, turn_timeout_secs: 300, @@ -2084,7 +2084,7 @@ fn pause_after_step_pauses_and_continue_resumes() -> Result<()> { commands::join_with_registry( &JoinArgs { session: session.clone(), - agent: "implementer".to_string(), + agent: "implement".to_string(), timeout: None, }, ®istry, @@ -2092,7 +2092,7 @@ fn pause_after_step_pauses_and_continue_resumes() -> Result<()> { commands::join_with_registry( &JoinArgs { session: session.clone(), - agent: "reviewer-1".to_string(), + agent: "review-1".to_string(), timeout: None, }, ®istry, @@ -2103,7 +2103,7 @@ fn pause_after_step_pauses_and_continue_resumes() -> Result<()> { commands::next_with_registry( &NextArgs { session: session.clone(), - agent: "implementer".to_string(), + agent: "implement".to_string(), timeout: Some(2), }, ®istry @@ -2120,7 +2120,7 @@ fn pause_after_step_pauses_and_continue_resumes() -> Result<()> { "--session", &session, "--as", - "implementer", + "implement", ], ®istry, )?; @@ -2134,7 +2134,7 @@ fn pause_after_step_pauses_and_continue_resumes() -> Result<()> { "--session", &session, "--as", - "reviewer-1", + "review-1", "--verdict", "approve", ], @@ -2153,7 +2153,7 @@ fn pause_after_step_pauses_and_continue_resumes() -> Result<()> { commands::next_with_registry( &NextArgs { session: session.clone(), - agent: "implementer".to_string(), + agent: "implement".to_string(), timeout: Some(2), }, ®istry @@ -2179,7 +2179,7 @@ fn pause_after_step_pauses_and_continue_resumes() -> Result<()> { commands::next_with_registry( &NextArgs { session: session.clone(), - agent: "implementer".to_string(), + agent: "implement".to_string(), timeout: Some(2), }, ®istry @@ -2196,7 +2196,7 @@ fn pause_after_step_pauses_and_continue_resumes() -> Result<()> { "--session", &session, "--as", - "implementer", + "implement", ], ®istry, )?; @@ -2211,7 +2211,7 @@ fn pause_after_step_pauses_and_continue_resumes() -> Result<()> { "--session", &session, "--as", - "reviewer-1", + "review-1", "--verdict", "approve", ], @@ -2234,7 +2234,7 @@ fn pause_after_step_pauses_and_continue_resumes() -> Result<()> { "--session", &session, "--as", - "reviewer-1", + "review-1", "--verdict", "approve", ], @@ -2246,7 +2246,7 @@ fn pause_after_step_pauses_and_continue_resumes() -> Result<()> { commands::next_with_registry( &NextArgs { session: session.clone(), - agent: "implementer".to_string(), + agent: "implement".to_string(), timeout: Some(2), }, ®istry