feat(#2067): choom uses a fresh named session, drop --fresh flag
This commit is contained in:
parent
ce959d7700
commit
09b83c9d1e
3 changed files with 41 additions and 114 deletions
|
|
@ -379,22 +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.
|
||||||
|
|
||||||
Replaces the current process with `machinectl shell <name>@h-<name>` running `claude --continue` from the agent's state dir — drops the operator straight into the agent's live Claude session with its full loaded context and persona. Requires root (same as all machinectl shell operations) and the container must be running.
|
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.
|
||||||
|
|
||||||
To match the harness exactly, choom enters **as the agent user** (not root) so claude reads the OAuth credentials from the agent's `/home/<name>/.claude`; runs from the agent's state dir (`/agents/<name>/state`) so `--continue` resumes the right per-project session and `CLAUDE.md` loads; and passes the same `--settings` / `--mcp-config` / `--system-prompt-file` the harness writes to `/run/hive-config/` (settings, MCP tools, role prompt). Entering as root (the `machinectl shell` default) loses all of it.
|
**Usage:** `hivectl choom <NAME>`
|
||||||
|
|
||||||
Pass `--fresh` to start a new Claude session instead of continuing the most recent one.
|
|
||||||
|
|
||||||
**Usage:** `hivectl choom [OPTIONS] <NAME>`
|
|
||||||
|
|
||||||
###### **Arguments:**
|
###### **Arguments:**
|
||||||
|
|
||||||
* `<NAME>` — Agent name (e.g. `damocles`, `iris`)
|
* `<NAME>` — Agent name (e.g. `damocles`, `iris`)
|
||||||
|
|
||||||
###### **Options:**
|
|
||||||
|
|
||||||
* `--fresh` — Start a fresh Claude session instead of continuing the most recent one. Without this flag `--continue` is passed to Claude so the operator joins the agent's ongoing session context
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## `hivectl stop`
|
## `hivectl stop`
|
||||||
|
|
|
||||||
|
|
@ -123,17 +123,17 @@ reported at the end rather than aborting mid-run.
|
||||||
|
|
||||||
Drop into an interactive Claude session inside an agent container.
|
Drop into an interactive Claude session inside an agent container.
|
||||||
Replaces the current process with `machinectl shell <name>@h-<name>`
|
Replaces the current process with `machinectl shell <name>@h-<name>`
|
||||||
running `claude --continue` from the agent's state dir. Requires root
|
running claude from the agent's state dir. Requires root (same as all
|
||||||
(same as all `machinectl shell` operations).
|
`machinectl shell` operations).
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
hivectl choom iris # join iris's ongoing Claude session (--continue)
|
hivectl choom iris # fresh Claude session named "choom" in iris's container
|
||||||
hivectl choom iris --fresh # start a new Claude session instead
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Without `--fresh`, `--continue` is passed so the operator joins the
|
Each launch starts a fresh session named `choom` (claude mints its own
|
||||||
agent's live context window. With `--fresh` a clean session starts.
|
id), so it never collides with the harness's live session in the same
|
||||||
The container must be running.
|
project dir. The operator can rejoin a prior choom session later with
|
||||||
|
`claude --resume choom`. 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:
|
||||||
|
|
@ -143,14 +143,13 @@ lands in a faithful copy of the agent's environment:
|
||||||
agent's `/home/<name>/.claude` where its OAuth credentials live. choom
|
agent's `/home/<name>/.claude` where its OAuth credentials live. choom
|
||||||
prefixes the machine with `<name>@` (the meta-flake sets the agent's
|
prefixes the machine with `<name>@` (the meta-flake sets the agent's
|
||||||
unix user name to its label).
|
unix user name to its label).
|
||||||
- **from `/agents/<name>/state`**: `--continue` resolves the session for
|
- **from `/agents/<name>/state`**: the session and `CLAUDE.md` (the
|
||||||
that project dir and `CLAUDE.md` (the persona) loads from the cwd.
|
persona) resolve against the right project dir.
|
||||||
- **with the harness flags**: `--settings`, `--mcp-config`, and
|
- **with the harness flags**: `--settings`, `--mcp-config`, and
|
||||||
`--system-prompt-file` from `/run/hive-config/` — the same files the
|
`--system-prompt-file` from `/run/hive-config/` — the same files the
|
||||||
harness writes each turn — so the operator gets the agent's settings,
|
harness writes each turn — so the operator gets the agent's settings,
|
||||||
the hyperhive/matrix MCP tools (which `--continue` needs to replay a
|
the hyperhive/matrix MCP tools, and the role prompt. Each flag is
|
||||||
tool-using history), and the role prompt. Each flag is included only
|
included only when its file exists.
|
||||||
when its file exists.
|
|
||||||
|
|
||||||
## Open
|
## Open
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -110,34 +110,15 @@ enum Cmd {
|
||||||
},
|
},
|
||||||
/// Open an interactive Claude session inside an agent container.
|
/// Open an interactive Claude session inside an agent container.
|
||||||
///
|
///
|
||||||
/// Replaces the current process with `machinectl shell
|
/// Execs `machinectl shell <name>@h-<name>` running claude as the
|
||||||
/// <name>@h-<name>` running claude from the agent's state dir — drops
|
/// agent user from its state dir, with the same settings / MCP config
|
||||||
/// the operator into a private Claude session in the agent's project,
|
/// / system prompt the harness uses. Each launch starts a fresh,
|
||||||
/// with its full settings, MCP tools, and persona. Requires root (same
|
/// named ("choom") session so it never collides with the harness's
|
||||||
/// as all machinectl shell operations) and the container must be
|
/// live session in the same project dir. Requires root + a running
|
||||||
/// running.
|
/// container.
|
||||||
///
|
|
||||||
/// To match the harness exactly, choom enters **as the agent user**
|
|
||||||
/// (not root) so claude reads the OAuth credentials from the agent's
|
|
||||||
/// `/home/<name>/.claude`; runs from the agent's state dir
|
|
||||||
/// (`/agents/<name>/state`) so the session is scoped to the right
|
|
||||||
/// project and `CLAUDE.md` loads; and passes the same `--settings` /
|
|
||||||
/// `--mcp-config` / `--system-prompt-file` the harness writes to
|
|
||||||
/// `/run/hive-config/` (settings, MCP tools, role prompt). Entering as
|
|
||||||
/// root (the `machinectl shell` default) loses all of it.
|
|
||||||
///
|
|
||||||
/// choom pins its OWN session id (not a bare `--continue`) so it never
|
|
||||||
/// resumes or corrupts the harness's live session in the same project
|
|
||||||
/// dir; re-running choom rejoins that private session. Pass `--fresh`
|
|
||||||
/// to reset it and start clean.
|
|
||||||
Choom {
|
Choom {
|
||||||
/// Agent name (e.g. `damocles`, `iris`).
|
/// Agent name (e.g. `damocles`, `iris`).
|
||||||
name: String,
|
name: String,
|
||||||
/// Reset choom's dedicated Claude session and start it clean.
|
|
||||||
/// Without this flag choom resumes its own prior session (separate
|
|
||||||
/// from the harness's live session), creating it on first use.
|
|
||||||
#[arg(long)]
|
|
||||||
fresh: bool,
|
|
||||||
},
|
},
|
||||||
/// 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,
|
||||||
|
|
@ -644,7 +625,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, fresh } => choom(&name, fresh),
|
Cmd::Choom { name } => choom(&name),
|
||||||
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,
|
||||||
|
|
@ -1037,45 +1018,25 @@ fn agent_exists(name: &str) -> Result<bool> {
|
||||||
|
|
||||||
/// Drop into an interactive Claude session in the agent container.
|
/// Drop into an interactive Claude session in the agent container.
|
||||||
///
|
///
|
||||||
/// Replaces the current process (exec) with `machinectl shell
|
/// Execs `machinectl shell <name>@h-<name> /bin/sh -lc '<script>'` where
|
||||||
/// <name>@h-<name> /bin/sh -lc '<script>'`, where the script `cd`s into
|
/// the script `cd`s into the agent's state dir and execs claude with the
|
||||||
/// the agent's state dir and execs claude with the *same* flags the
|
/// same flags the harness uses. Four things matter:
|
||||||
/// harness uses. Three things matter here, all of which the naive
|
|
||||||
/// `machinectl shell h-<name> claude` got wrong (issue: choom missing
|
|
||||||
/// creds + settings):
|
|
||||||
///
|
///
|
||||||
/// 1. **Run as the agent user.** `machinectl shell` defaults to root in
|
/// 1. **Run as the agent user** (`<name>@` prefix), not root, so claude
|
||||||
/// the container, so claude would read `/root/.claude` (empty)
|
/// reads the agent's `/home/<name>/.claude` OAuth creds.
|
||||||
/// instead of the agent's `/home/<name>/.claude` where the OAuth
|
/// 2. **Start in the agent's state dir** (`/agents/<name>/state`) so the
|
||||||
/// credentials live. Prefixing the machine with `<name>@` enters the
|
/// session + `CLAUDE.md` resolve against the right project.
|
||||||
/// session as the agent user (the meta-flake sets
|
/// 3. **Pass the harness's flags** (`--settings` / `--mcp-config` /
|
||||||
/// `hyperhive.user.name` to the agent label, so the unix user name
|
/// `--system-prompt-file` from `/run/hive-config/`), each only if its
|
||||||
/// matches the agent name).
|
/// file exists so a half-up container degrades to a bare session.
|
||||||
/// 2. **Start in the agent's state dir.** A claude session is scoped to
|
/// 4. **Use a fresh, named session.** `--name choom` starts a brand-new
|
||||||
/// the project directory it ran in, and project memory (`CLAUDE.md`)
|
/// session (claude mints a random id) tagged "choom", so it never
|
||||||
/// is read from the cwd. The harness runs claude from
|
/// resumes the harness's live session in this shared project dir. The
|
||||||
/// `/agents/<name>/state`, so choom has to `cd` there or its session +
|
/// operator can rejoin a prior choom session later via `claude
|
||||||
/// persona resolve against the wrong project.
|
/// --resume choom`.
|
||||||
/// 3. **Pass the harness's claude flags.** The harness drops
|
|
||||||
/// `claude-{settings.json,mcp-config.json,system-prompt.md}` into
|
|
||||||
/// `/run/hive-config/` (`hive-ag3nt::paths::config_dir()`) and runs
|
|
||||||
/// claude with `--settings` / `--mcp-config` / `--system-prompt-file`
|
|
||||||
/// pointing at them. Skipping them gives the operator default settings,
|
|
||||||
/// no hyperhive/matrix MCP tools (needed to replay a tool-using
|
|
||||||
/// history), and no role prompt. choom mirrors those flags; each is
|
|
||||||
/// added only if the file exists, so a mid-restart container degrades
|
|
||||||
/// to a bare session instead of erroring.
|
|
||||||
/// 4. **Pin choom's own session (NOT bare `--continue`).** The harness's
|
|
||||||
/// live session is pinned to a claude-assigned id in this same project
|
|
||||||
/// dir; a bare `--continue` would resume the most-recent session there
|
|
||||||
/// and let choom + the harness clobber each other's history. choom
|
|
||||||
/// instead uses a fixed sentinel session id (`--resume` it if present,
|
|
||||||
/// else `--session-id` to create it), so it gets a private session that
|
|
||||||
/// never collides with the harness's. `--fresh` resets that session.
|
|
||||||
///
|
///
|
||||||
/// `machinectl shell` inherits the caller's PTY, so the session is
|
/// Inherits the caller's PTY. Requires root + a running container.
|
||||||
/// fully interactive. Requires root and a running container.
|
fn choom(name: &str) -> Result<()> {
|
||||||
fn choom(name: &str, fresh: bool) -> 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/)");
|
||||||
}
|
}
|
||||||
|
|
@ -1092,43 +1053,18 @@ fn choom(name: &str, fresh: bool) -> 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";
|
||||||
// choom pins its OWN claude session under a fixed sentinel id so it
|
|
||||||
// never resumes — and thus never corrupts — the harness's live session,
|
|
||||||
// which shares this exact project dir (`state_dir`). The harness session
|
|
||||||
// id is claude-assigned (random, captured + `--resume`d each turn; see
|
|
||||||
// hive-ag3nt turn.rs), so it can't collide with this id. A single fixed
|
|
||||||
// id is safe for every agent because containers are isolated (each has
|
|
||||||
// its own `$HOME/.claude`).
|
|
||||||
let choom_session_id = "c0c0c0c0-0000-4000-8000-c0ffeec0ffee";
|
|
||||||
// `--fresh` drops the stored choom session and starts it clean; the
|
|
||||||
// default resumes it when a session file for the id exists (so the
|
|
||||||
// operator rejoins their own prior choom context), creating it on first
|
|
||||||
// use. `find -name <id>.jsonl` matches regardless of claude's project
|
|
||||||
// slug, since the id is unique to choom.
|
|
||||||
let session_setup = if fresh {
|
|
||||||
format!(
|
|
||||||
"find \"$HOME/.claude/projects\" -name {choom_session_id}.jsonl -delete 2>/dev/null; \
|
|
||||||
set -- \"$@\" --session-id {choom_session_id}; "
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
format!(
|
|
||||||
"if find \"$HOME/.claude/projects\" -name {choom_session_id}.jsonl -print -quit 2>/dev/null | grep -q .; \
|
|
||||||
then set -- \"$@\" --resume {choom_session_id}; \
|
|
||||||
else set -- \"$@\" --session-id {choom_session_id}; fi; "
|
|
||||||
)
|
|
||||||
};
|
|
||||||
// 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.
|
||||||
// `cd` so the pinned session + CLAUDE.md resolve against the agent's
|
// `--name choom` starts a fresh, named session each launch (claude
|
||||||
// project; `exec` hands the PTY to claude directly.
|
// mints its own id), so choom never collides with the harness's live
|
||||||
|
// session in this same project dir; `--resume choom` rejoins it later.
|
||||||
let inner = format!(
|
let inner = format!(
|
||||||
"cd {state_dir} || exit 1; set --; \
|
"cd {state_dir} || exit 1; set -- --name choom; \
|
||||||
[ -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; \
|
||||||
{session_setup}\
|
|
||||||
exec {claude} \"$@\""
|
exec {claude} \"$@\""
|
||||||
);
|
);
|
||||||
let mut cmd = std::process::Command::new("machinectl");
|
let mut cmd = std::process::Command::new("machinectl");
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue