feat(#2067): choom takes optional session id to resume, else blank session
This commit is contained in:
parent
09b83c9d1e
commit
c5847e25ea
3 changed files with 51 additions and 23 deletions
|
|
@ -379,13 +379,14 @@ Generate the federation peer-config block for THIS hive — the nix a peer opera
|
||||||
|
|
||||||
Open an interactive Claude session inside an agent container.
|
Open an interactive Claude session inside an agent container.
|
||||||
|
|
||||||
Execs `machinectl shell <name>@h-<name>` running claude as the agent user from its state dir, with the same settings / MCP config / system prompt the harness uses. Each launch starts a fresh, named ("choom") session so it never collides with the harness's live session in the same project dir. Requires root + a running container.
|
Execs `machinectl shell <name>@h-<name>` running claude as the agent user from its state dir, with the same settings / MCP config / system prompt the harness uses. Without a session id you get a fresh blank session each launch; pass one to resume an earlier choom session (`--resume <id>`). Either way it never collides with the harness's live session — the harness pins its own session id via `--resume`, so a blank choom session is invisible to it. Requires root + a running container.
|
||||||
|
|
||||||
**Usage:** `hivectl choom <NAME>`
|
**Usage:** `hivectl choom <NAME> [SESSION]`
|
||||||
|
|
||||||
###### **Arguments:**
|
###### **Arguments:**
|
||||||
|
|
||||||
* `<NAME>` — Agent name (e.g. `damocles`, `iris`)
|
* `<NAME>` — Agent name (e.g. `damocles`, `iris`)
|
||||||
|
* `<SESSION>` — Optional session id to resume (`claude --resume <id>`). Omit for a fresh blank session
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -127,13 +127,16 @@ running claude from the agent's state dir. Requires root (same as all
|
||||||
`machinectl shell` operations).
|
`machinectl shell` operations).
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
hivectl choom iris # fresh Claude session named "choom" in iris's container
|
hivectl choom iris # fresh blank Claude session in iris's container
|
||||||
|
hivectl choom iris <session-id> # resume an earlier choom session
|
||||||
```
|
```
|
||||||
|
|
||||||
Each launch starts a fresh session named `choom` (claude mints its own
|
With no session id, claude starts a fresh blank session (mints its own
|
||||||
id), so it never collides with the harness's live session in the same
|
id). Pass a session id to resume it (`claude --resume <id>`); you can
|
||||||
project dir. The operator can rejoin a prior choom session later with
|
rename it in-session. Either way choom never collides with the harness's
|
||||||
`claude --resume choom`. The container must be running.
|
live session in the same project dir: the harness pins its own id via
|
||||||
|
`--resume`, so a blank choom session is invisible to it. The container
|
||||||
|
must be running.
|
||||||
|
|
||||||
choom reproduces the harness's own claude invocation so the operator
|
choom reproduces the harness's own claude invocation so the operator
|
||||||
lands in a faithful copy of the agent's environment:
|
lands in a faithful copy of the agent's environment:
|
||||||
|
|
|
||||||
|
|
@ -112,13 +112,18 @@ enum Cmd {
|
||||||
///
|
///
|
||||||
/// Execs `machinectl shell <name>@h-<name>` running claude as the
|
/// Execs `machinectl shell <name>@h-<name>` running claude as the
|
||||||
/// agent user from its state dir, with the same settings / MCP config
|
/// agent user from its state dir, with the same settings / MCP config
|
||||||
/// / system prompt the harness uses. Each launch starts a fresh,
|
/// / system prompt the harness uses. Without a session id you get a
|
||||||
/// named ("choom") session so it never collides with the harness's
|
/// fresh blank session each launch; pass one to resume an earlier
|
||||||
/// live session in the same project dir. Requires root + a running
|
/// choom session (`--resume <id>`). Either way it never collides with
|
||||||
/// container.
|
/// the harness's live session — the harness pins its own session id
|
||||||
|
/// via `--resume`, so a blank choom session is invisible to it.
|
||||||
|
/// Requires root + a running container.
|
||||||
Choom {
|
Choom {
|
||||||
/// Agent name (e.g. `damocles`, `iris`).
|
/// Agent name (e.g. `damocles`, `iris`).
|
||||||
name: String,
|
name: String,
|
||||||
|
/// Optional session id to resume (`claude --resume <id>`). Omit
|
||||||
|
/// for a fresh blank session.
|
||||||
|
session: Option<String>,
|
||||||
},
|
},
|
||||||
/// Stop containers hive-wide in one operator action. Bare `hivectl
|
/// Stop containers hive-wide in one operator action. Bare `hivectl
|
||||||
/// stop` stops **everything** — all sub-agents plus the ci, forge,
|
/// stop` stops **everything** — all sub-agents plus the ci, forge,
|
||||||
|
|
@ -625,7 +630,7 @@ async fn main() -> Result<()> {
|
||||||
Cmd::Subvol { cmd } => match cmd {
|
Cmd::Subvol { cmd } => match cmd {
|
||||||
SubvolCmd::Upgrade { name, yes } => subvol_upgrade(&socket, &name, yes).await,
|
SubvolCmd::Upgrade { name, yes } => subvol_upgrade(&socket, &name, yes).await,
|
||||||
},
|
},
|
||||||
Cmd::Choom { name } => choom(&name),
|
Cmd::Choom { name, session } => choom(&name, session.as_deref()),
|
||||||
Cmd::Quota { cmd } => match cmd {
|
Cmd::Quota { cmd } => match cmd {
|
||||||
QuotaCmd::Enable => quota_enable().await,
|
QuotaCmd::Enable => quota_enable().await,
|
||||||
QuotaCmd::Show { name } => quota_show(name.as_deref()).await,
|
QuotaCmd::Show { name } => quota_show(name.as_deref()).await,
|
||||||
|
|
@ -1029,14 +1034,15 @@ fn agent_exists(name: &str) -> Result<bool> {
|
||||||
/// 3. **Pass the harness's flags** (`--settings` / `--mcp-config` /
|
/// 3. **Pass the harness's flags** (`--settings` / `--mcp-config` /
|
||||||
/// `--system-prompt-file` from `/run/hive-config/`), each only if its
|
/// `--system-prompt-file` from `/run/hive-config/`), each only if its
|
||||||
/// file exists so a half-up container degrades to a bare session.
|
/// file exists so a half-up container degrades to a bare session.
|
||||||
/// 4. **Use a fresh, named session.** `--name choom` starts a brand-new
|
/// 4. **Pick the session explicitly.** With no `session` arg, claude
|
||||||
/// session (claude mints a random id) tagged "choom", so it never
|
/// starts a fresh blank session (mints a random id). Pass a session
|
||||||
/// resumes the harness's live session in this shared project dir. The
|
/// id to resume it (`--resume <id>`); the operator can rename it
|
||||||
/// operator can rejoin a prior choom session later via `claude
|
/// in-session. Either way choom never clobbers the harness's live
|
||||||
/// --resume choom`.
|
/// session: the harness pins its own id via `--resume`, so a blank
|
||||||
|
/// choom session in this shared project dir is invisible to it.
|
||||||
///
|
///
|
||||||
/// Inherits the caller's PTY. Requires root + a running container.
|
/// Inherits the caller's PTY. Requires root + a running container.
|
||||||
fn choom(name: &str) -> Result<()> {
|
fn choom(name: &str, session: Option<&str>) -> Result<()> {
|
||||||
if !agent_exists(name)? {
|
if !agent_exists(name)? {
|
||||||
bail!("no such agent: '{name}' (no state dir under /var/lib/hyperhive/agents/)");
|
bail!("no such agent: '{name}' (no state dir under /var/lib/hyperhive/agents/)");
|
||||||
}
|
}
|
||||||
|
|
@ -1053,15 +1059,33 @@ fn choom(name: &str) -> Result<()> {
|
||||||
// `hive-config`); matches `hive-ag3nt::paths::config_dir()`. These
|
// `hive-config`); matches `hive-ag3nt::paths::config_dir()`. These
|
||||||
// are the exact files the harness passes to claude each turn.
|
// are the exact files the harness passes to claude each turn.
|
||||||
let cfg = "/run/hive-config";
|
let cfg = "/run/hive-config";
|
||||||
|
// A session id (if given) is resumed via `--resume <id>`; with none,
|
||||||
|
// claude starts a fresh blank session. The id is interpolated into
|
||||||
|
// the shell script, so constrain it to a safe charset first to rule
|
||||||
|
// out injection (real session ids are UUIDs).
|
||||||
|
let session_arg = match session {
|
||||||
|
Some(sid) => {
|
||||||
|
if sid.is_empty()
|
||||||
|
|| !sid
|
||||||
|
.bytes()
|
||||||
|
.all(|b| b.is_ascii_alphanumeric() || b"-_.".contains(&b))
|
||||||
|
{
|
||||||
|
bail!(
|
||||||
|
"invalid session id '{sid}': expected a claude session id (alphanumerics, '-', '_', '.')"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
format!("set -- --resume {sid};")
|
||||||
|
}
|
||||||
|
None => "set --;".to_string(),
|
||||||
|
};
|
||||||
// Build the claude argv as the harness does, but only include each
|
// Build the claude argv as the harness does, but only include each
|
||||||
// flag when its file is present so a half-up container falls back to
|
// flag when its file is present so a half-up container falls back to
|
||||||
// a bare session rather than a hard claude error. `name` is
|
// a bare session rather than a hard claude error. `name` is
|
||||||
// constrained to `[a-z0-9._-]` so there's nothing to quote.
|
// constrained to `[a-z0-9._-]` so there's nothing to quote. choom
|
||||||
// `--name choom` starts a fresh, named session each launch (claude
|
// never collides with the harness's live session in this same project
|
||||||
// mints its own id), so choom never collides with the harness's live
|
// dir: the harness pins its own session id via `--resume`.
|
||||||
// session in this same project dir; `--resume choom` rejoins it later.
|
|
||||||
let inner = format!(
|
let inner = format!(
|
||||||
"cd {state_dir} || exit 1; set -- --name choom; \
|
"cd {state_dir} || exit 1; {session_arg} \
|
||||||
[ -f {cfg}/claude-settings.json ] && set -- \"$@\" --settings {cfg}/claude-settings.json; \
|
[ -f {cfg}/claude-settings.json ] && set -- \"$@\" --settings {cfg}/claude-settings.json; \
|
||||||
[ -f {cfg}/claude-mcp-config.json ] && set -- \"$@\" --mcp-config {cfg}/claude-mcp-config.json; \
|
[ -f {cfg}/claude-mcp-config.json ] && set -- \"$@\" --mcp-config {cfg}/claude-mcp-config.json; \
|
||||||
[ -f {cfg}/claude-system-prompt.md ] && set -- \"$@\" --system-prompt-file {cfg}/claude-system-prompt.md; \
|
[ -f {cfg}/claude-system-prompt.md ] && set -- \"$@\" --system-prompt-file {cfg}/claude-system-prompt.md; \
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue