Watch
0
0
Fork
You've already forked hyperhive
0

Point agents at /agents/<name>/work and default subagents to it

The system prompt now names the work dir for worktrees, clones and build
trees, and keeps state/ for notes. A subagent started without a dir runs
in the agent's work dir instead of the daemon's own cwd.
This commit is contained in:
atlas 2026-10-15 21:18:05 +02:00 • committed by mara
commit 043c9445f6
7 changed files with 32 additions and 12 deletions

View file

@ -79,8 +79,9 @@ struct StartArgs {
trigger: String,
/// Working directory for the subagent's session — e.g. a git worktree
/// you've already prepared for it, so a parallel batch of subagents
/// never race on the same working tree. Must exist. Omit to inherit
/// this daemon's own working directory (today's default). The daemon
/// never race on the same working tree. Must exist. Omit to use your
/// work dir, `/agents/<you>/work` (it has no `CLAUDE.md` above it, so the
/// subagent does not load yours). The daemon
/// remembers whichever `dir` you give here against `name`, so a later
/// `continue`/`status` for the same name doesn't need to repeat it —
/// only pass it there again if you want to point at a *different*
@ -164,7 +165,7 @@ struct ContinueArgs {
/// is exactly when you're most likely to be reaching for `continue`. So
/// pass it when pointing the session at a *different* directory than
/// last time, and pass it again after a restart if the session lives
/// anywhere other than the daemon's own working directory. A name with no
/// anywhere other than your work dir. A name with no
/// session in the directory searched is refused, naming it.
#[serde(default)]
dir: Option<String>,

View file

@ -30,6 +30,15 @@ pub fn state_dir() -> PathBuf {
PathBuf::from(format!("/agents/{label}/state"))
}
/// The agent's persistent work dir, a sibling of [`state_dir`]
/// (`/agents/<name>/work`). It has no `CLAUDE.md` above it, so a subagent
/// started here does not load the agent's own. This is the working
/// directory a subagent gets when `start` is given no `dir`.
#[must_use]
pub fn work_dir() -> PathBuf {
state_dir().with_file_name("work")
}
/// Where this agent's named subagent role files live, one markdown file per
/// role under the agent's own [`state_dir`]. Per-agent rather than
/// hive-wide: the clauses a role carries came out of one agent's own prompt

View file

@ -15,7 +15,7 @@
//! session, found again by name. `continue` is how a caller reattaches to
//! it, whether that's "give it a new turn" or "the daemon restarted and I
//! want to pick this back up" — after a restart, re-supply `dir` once if
//! the session isn't in the daemon's own default directory.
//! the session isn't in the agent's work dir (the default).
//!
//! **A running turn is not necessarily a working turn.** A wedged child
//! satisfies "running" as fully as a busy one, so every line of the child's
@ -1185,7 +1185,7 @@ fn subagent_otel_attrs(name: &str) -> String {
/// skill already asks of `model`. `prompt_file`, when given, becomes
/// `--append-system-prompt-file` — the subagent's task instructions.
/// `dir`, when given, becomes `Config::cwd` (e.g. a worktree the caller
/// already prepared); `None` inherits this daemon's own working directory.
/// already prepared); `None` falls back to [`crate::paths::work_dir`].
/// Always `--dangerously-skip-permissions --strict-mcp-config --tools` — two
/// gates covering one half each, neither substituting for the other:
/// `strict_mcp_config` over **MCP** tools (no ambient discovery, so exactly
@ -1239,7 +1239,7 @@ fn build_config(
Config {
model,
effort: Some(effort.unwrap_or_else(|| "medium".to_owned())),
cwd: dir.map(PathBuf::from),
cwd: Some(dir.map_or_else(crate::paths::work_dir, PathBuf::from)),
mcp_config: crate::mcp_config::build(name, signal_url),
strict_mcp_config: true,
extra_args,
@ -1387,7 +1387,7 @@ pub struct StartRequest {
pub role: Option<String>,
/// The first turn's prompt. A goal, when given, is appended to it.
pub trigger: String,
/// Working directory for the session; `None` inherits the daemon's.
/// Working directory for the session; `None` means the agent's work dir.
pub dir: Option<String>,
/// What this session is being continued *toward*. `None` keeps the
/// pre-continuation shape: one turn, one todo, no re-prompting.
@ -3345,12 +3345,12 @@ mod tests {
}
#[test]
fn build_config_sets_cwd_only_when_a_dir_is_given() {
fn build_config_defaults_cwd_to_the_work_dir_when_no_dir_is_given() {
let with = build_config("n", None, None, None, Some("/tmp/some-worktree"), None);
assert_eq!(with.cwd, Some(PathBuf::from("/tmp/some-worktree")));
let without = build_config("n", None, None, None, None, None);
assert_eq!(without.cwd, None);
assert_eq!(without.cwd, Some(crate::paths::work_dir()));
}
#[test]