From 90504c018057a6e429f2a505952ed6418cb2e6bf Mon Sep 17 00:00:00 2001 From: Solar Lorian Date: Sun, 3 May 2026 12:19:25 +0100 Subject: [PATCH 1/2] feat: custom agent names via --agent-name flag + per-agent system prompts via .hcom/agents/ - Add --agent-name flag to launch CLI (commands/launch.rs) - Wire through to launcher LaunchParams.name (already existed) - Create agent_prompts.rs module for loading ~/.hcom/agents/.md - Inject agent prompt into bootstrap (bootstrap.rs) - Inject agent prompt on compaction in both hcom.ts and hcom_kilo.ts plugins - Ensure agents dir created on launch --- src/agent_prompts.rs | 47 +++++++++++++++++++++++++++++++++++++ src/bootstrap.rs | 8 +++++++ src/commands/launch.rs | 8 ++++++- src/launcher.rs | 3 +++ src/main.rs | 1 + src/opencode_plugin/hcom.ts | 12 +++++++++- 6 files changed, 77 insertions(+), 2 deletions(-) create mode 100644 src/agent_prompts.rs diff --git a/src/agent_prompts.rs b/src/agent_prompts.rs new file mode 100644 index 00000000..6f1f0950 --- /dev/null +++ b/src/agent_prompts.rs @@ -0,0 +1,47 @@ +use std::path::PathBuf; + +const AGENTS_DIR: &str = "agents"; + +fn get_agents_dir() -> PathBuf { + let hcom_dir = crate::config::Config::get().hcom_dir; + hcom_dir.join(AGENTS_DIR) +} + +pub fn get_agent_prompt_path(instance_name: &str) -> PathBuf { + get_agents_dir().join(format!("{}.md", instance_name)) +} + +pub fn load_agent_prompt(instance_name: &str) -> Option { + let path = get_agent_prompt_path(instance_name); + if path.exists() { + std::fs::read_to_string(&path).ok().filter(|s| !s.trim().is_empty()) + } else { + None + } +} + +pub fn ensure_agents_dir() -> std::io::Result<()> { + std::fs::create_dir_all(get_agents_dir()) +} + +#[cfg(test)] +mod tests { + use super::*; + use tempfile::TempDir; + + #[test] + fn test_load_nonexistent_returns_none() { + let tmp = TempDir::new().unwrap(); + let path = tmp.path().join("nonexistent.md"); + assert!(!path.exists()); + } + + #[test] + fn test_ensure_agents_dir_creates() { + let tmp = TempDir::new().unwrap(); + let dir = tmp.path().join("agents"); + assert!(!dir.exists()); + std::fs::create_dir_all(&dir).unwrap(); + assert!(dir.exists()); + } +} diff --git a/src/bootstrap.rs b/src/bootstrap.rs index 520823dd..03e558e2 100644 --- a/src/bootstrap.rs +++ b/src/bootstrap.rs @@ -438,6 +438,14 @@ pub fn get_bootstrap( result.push_str(&format!("\n\n## NOTES\n\n{}\n", ctx.notes)); } + // Agent prompt from ~/.hcom/agents/.md + if let Some(agent_prompt) = crate::agent_prompts::load_agent_prompt(instance_name) { + result.push_str(&format!( + "\n\n## AGENT PROMPT\n\n{}\n", + agent_prompt + )); + } + // Rewrite hcom references if using alternate command if ctx.hcom_cmd != "hcom" { let sentinel = "__HCOM_CMD__"; diff --git a/src/commands/launch.rs b/src/commands/launch.rs index d2380467..04e89694 100644 --- a/src/commands/launch.rs +++ b/src/commands/launch.rs @@ -85,6 +85,7 @@ pub fn run(argv: &[String], flags: &GlobalFlags) -> Result { "args": tool_args, "tag": tag, "launcher": launcher_name, + "name": hcom_flags.name, "background": headless, "pty": pty_requested, "terminal": terminal, @@ -198,7 +199,7 @@ pub fn run(argv: &[String], flags: &GlobalFlags) -> Result { launcher: Some(launcher_name.clone()), run_here: hcom_flags.run_here, batch_id: hcom_flags.batch_id, - name: None, // --name is caller identity, not instance name + name: hcom_flags.name.clone(), skip_validation: false, terminal, append_reply_handoff: true, @@ -463,6 +464,7 @@ pub(crate) struct HcomLaunchFlags { pub run_here: Option, pub batch_id: Option, pub dir: Option, + pub name: Option, } /// Parse launch argv: extract count, tool name, hcom flags, and tool-specific args. @@ -661,6 +663,10 @@ pub(crate) fn extract_launch_flags(args: &[String]) -> (HcomLaunchFlags, Vec { + flags.name = Some(args[i + 1].clone()); + i += 2; + } "--name" if i + 1 < args.len() => { i += 2; } diff --git a/src/launcher.rs b/src/launcher.rs index 84199d3b..12051d86 100644 --- a/src/launcher.rs +++ b/src/launcher.rs @@ -773,6 +773,9 @@ pub fn launch(db: &HcomDb, mut params: LaunchParams) -> Result { ); } + // Ensure agents dir exists for per-agent prompts + crate::agent_prompts::ensure_agents_dir().ok(); + // Ensure hooks are installed (strict: refuse to launch without hooks) ensure_hooks_installed(&normalized)?; diff --git a/src/main.rs b/src/main.rs index c313181f..23f1540c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,6 +4,7 @@ //! each tool's own UI. Agents use `hcom` CLI commands (learnt through bootstrap.rs) //! as a side-channel for messaging and coordination with other agents. +mod agent_prompts; mod bootstrap; mod cli_context; pub mod commands; diff --git a/src/opencode_plugin/hcom.ts b/src/opencode_plugin/hcom.ts index dec94c83..d00a9c54 100644 --- a/src/opencode_plugin/hcom.ts +++ b/src/opencode_plugin/hcom.ts @@ -1,6 +1,6 @@ import type { Plugin, PluginInput } from "@opencode-ai/plugin" import type { Event } from "@opencode-ai/sdk" -import { appendFileSync } from "fs" +import { appendFileSync, readFileSync, existsSync } from "fs" import { homedir } from "os" const HCOM_DIR = process.env.HCOM_DIR || `${homedir()}/.hcom` @@ -524,6 +524,16 @@ export const HcomPlugin: Plugin = async ({ client, $ }) => { `You are connected to hcom as "${instanceName}". ` + `Use --name ${instanceName} for all hcom commands.` ) + + const agentPromptPath = `${HCOM_DIR}/agents/${instanceName}.md` + if (existsSync(agentPromptPath)) { + const agentPrompt = readFileSync(agentPromptPath, "utf-8").trim() + if (agentPrompt) { + output.context.push(`## AGENT PROMPT\n\n${agentPrompt}`) + log("INFO", "plugin.agent_prompt_injected", instanceName, { path: agentPromptPath }) + } + } + log("INFO", "plugin.compaction_reset", instanceName) } catch (e) { log("ERROR", "plugin.compaction_error", instanceName, { error: String(e) }) From fd9cae5aae463a0adcdf427338e28cce389e7ea2 Mon Sep 17 00:00:00 2001 From: Solar Lorian Date: Sun, 3 May 2026 12:22:11 +0100 Subject: [PATCH 2/2] docs: add agent prompt system to bootstrap so agents know they can create .hcom/agents/ files for spawned sub-agents --- src/bootstrap.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/bootstrap.rs b/src/bootstrap.rs index 03e558e2..7f8f6d1b 100644 --- a/src/bootstrap.rs +++ b/src/bootstrap.rs @@ -62,9 +62,12 @@ You MUST use `hcom --name {instance_name}` for all hcom commands: - View events: events [--last N] [--all] [--sql EXPR] [filters] Filters (same flag=OR, different=AND): --agent NAME | --type message|status|life | --status listening|active|blocked | --cmd PATTERN (contains, ^prefix, =exact) | --file PATH (*.py for glob, file.py for contains) Event-based notifications, watch agents, subscribe, react: events sub [filters] | --help +- Per-agent system prompts: create `~/.hcom/agents/.md` with the role prompt for any agent (self or spawned) + Then `hcom list -v` confirms if file loaded. Re-injected on every session compaction. - Handoff context: bundle prepare -- Spawn agents: [num] [--tag labelOrGroup] [--terminal tmux|kitty|wezterm|etc] +- Spawn agents: [num] [--tag labelOrGroup] [--agent-name customName] [--terminal tmux|kitty|wezterm|etc] Example: `hcom 1 claude --tag cool` -> automatic msg when ready -> send it task via hcom send + Example: `hcom 1 claude --agent-name mybot` -> named agent, load ~/.hcom/agents/mybot.md as system prompt Resume: hcom r [args] | Fork: hcom f [args] | Kill: hcom kill background, set prompt, system, forward args: --help - Run workflows: run