Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
99867195e5 | ||
|
|
9af5234f74 |
3 changed files with 106 additions and 8 deletions
13
CLAUDE.md
13
CLAUDE.md
|
|
@ -156,7 +156,18 @@ in.
|
|||
listing containers (deep-linked to their per-container UI) + pending
|
||||
approvals. Same aesthetic.
|
||||
- Firewall opens 7000, 8000, 8100–8999 when the module is enabled.
|
||||
- 🔜 Phase 7 — dashboard approval-actions + live message-flow + xterm.js terminal
|
||||
- ✅ Phase 7 — polish:
|
||||
- 7a — dashboard `POST /approve/<id>` / `/deny/<id>` buttons + unified
|
||||
diff (via `similar`) of applied vs proposed `agent.nix`.
|
||||
- 7b — broker broadcast channel + `/messages/stream` SSE + live message-flow
|
||||
panel (cyan `→` sent / green `✓` delivered, 200 row cap).
|
||||
- 7c — `ApprovalResolved` helper events into the manager's inbox
|
||||
(`SYSTEM_SENDER` + `HelperEvent` JSON). Manager harness recognises and
|
||||
logs them distinctly.
|
||||
- 7d — default `MemoryMax=2G` + `CPUQuota=50%` applied to every managed
|
||||
container via `/run/systemd/system/container@<NAME>.service.d/hyperhive-limits.conf`
|
||||
drop-in (regenerated on every spawn / rebuild).
|
||||
- 7e — damocles migration plan (`docs/damocles-migration.md`).
|
||||
|
||||
## Approval flow (Phase 5)
|
||||
|
||||
|
|
|
|||
73
docs/damocles-migration.md
Normal file
73
docs/damocles-migration.md
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
# Migrating damocles onto hyperhive
|
||||
|
||||
The plan calls out damocles → hyperhive as a future migration. This doc lays
|
||||
out the options + recommended path. Not yet executed.
|
||||
|
||||
## Current state (separate from hyperhive)
|
||||
|
||||
`damocles` is a declarative nixos-container on `muede-lpt2`:
|
||||
|
||||
- Declared in `nixosConfigurations/muede-lpt2/containers.nix`
|
||||
- Built from `nixosConfigurations/damocles/` (claude-container.nix + extras)
|
||||
- Bind-mounts (RO unless noted):
|
||||
- `/etc/nix/distributed-build-key`
|
||||
- `/persist/damocles-ssh`
|
||||
- `/persist/damocles-lab` (RW — persistent work dir)
|
||||
- `privateNetwork = false`
|
||||
- Has its own systemd-services override (`TimeoutStopSec = 60s`, `RestartSec = 5s`)
|
||||
- Hosts the user's primary day-job Claude Code session, not part of the swarm
|
||||
|
||||
## Options
|
||||
|
||||
### A. Sub-agent under hive-c0re
|
||||
Make damocles a `hive-agent-damocles` (or whatever short name fits the 9-char cap).
|
||||
hive-c0re owns its lifecycle; its config flake is the manager-editable
|
||||
`/var/lib/hyperhive/agents/damocles/config/`.
|
||||
|
||||
Pros: uniform — message broker, dashboard, approval flow apply to damocles too.
|
||||
Cons: a lot of damocles-specific state (bind-mounts, ssh keys, build keys) has to
|
||||
be modeled as per-agent config. Today's `agent.nix` schema doesn't support
|
||||
declaring bind-mounts; would need to extend. And the user's day-job session
|
||||
becoming subject to hive-c0re lifecycle (restarts on rebuild) is invasive.
|
||||
|
||||
### B. Peer container (broker-integrated, lifecycle-independent)
|
||||
Keep damocles declarative; have its harness install `hive-ag3nt` and connect to
|
||||
the broker via a bind-mounted socket. damocles can send/recv messages with
|
||||
other sub-agents but is not managed by hive-c0re.
|
||||
|
||||
Pros: low blast radius. damocles keeps its bind-mounts + its own restart policy.
|
||||
Manager can route messages to it (it's just another inbox key on the broker).
|
||||
Cons: two lifecycle mechanisms coexist forever; "damocles" doesn't appear in the
|
||||
dashboard's container list (it filters `hive-` and `hm1nd`).
|
||||
|
||||
### C. Don't migrate
|
||||
damocles stays out of hyperhive. The two systems coexist; the user's day-job
|
||||
Claude and the swarm are deliberately separate.
|
||||
|
||||
Pros: zero work; aligns with the actual usage pattern (day-job vs. experiment).
|
||||
Cons: no message routing between damocles and the swarm.
|
||||
|
||||
## Recommended
|
||||
|
||||
**C for now, B once cross-pollination is wanted.** Hyperhive's invariants
|
||||
(11-char container names, manager-driven lifecycle, sealed `applied/` config)
|
||||
fit poorly with damocles's role as the user's working Claude. Wait until there's
|
||||
a concrete reason to wire them together (e.g. "I want to ask hm1nd from inside
|
||||
damocles") and then do B — extend the broker socket bind into damocles and
|
||||
install `hive-ag3nt` there. No need to subsume damocles under hive-c0re.
|
||||
|
||||
If/when option B is taken:
|
||||
|
||||
1. Add `${hyperhive.packages.${system}.default}/bin/hive-ag3nt` (or just
|
||||
`pkgs.hyperhive`) to `nixosConfigurations/damocles/claude-container.nix`.
|
||||
2. Bind-mount `/run/hyperhive/agents/damocles/` into damocles at `/run/hive/`.
|
||||
On the host, this is just another dir hive-c0re needs to know about —
|
||||
maybe expose a "peer agents" registration mechanism in the broker.
|
||||
3. Run `hive-ag3nt serve` as a systemd unit inside damocles (separate from
|
||||
the user's interactive claude session — broker peer, not turn-loop).
|
||||
4. Optionally: add a `hive-c0re register-peer damocles` admin verb so the
|
||||
container appears in `list()` and the dashboard. (Or just hard-list it.)
|
||||
|
||||
A is on the table only if the user's workflow shifts toward "the swarm IS the
|
||||
day-job environment" — at which point damocles dissolves into a `hive-agent-*`
|
||||
naturally.
|
||||
|
|
@ -157,14 +157,22 @@ async fn render_approvals(approvals: &[Approval]) -> String {
|
|||
}
|
||||
|
||||
async fn approval_diff(agent: &str, commit_ref: &str) -> String {
|
||||
let applied = Coordinator::agent_applied_dir(agent).join("agent.nix");
|
||||
let proposed = Coordinator::agent_proposed_dir(agent);
|
||||
if !proposed.exists() {
|
||||
return format!(
|
||||
"(proposed dir {} does not exist — agent destroyed?)",
|
||||
proposed.display()
|
||||
);
|
||||
}
|
||||
if !proposed.join(".git").exists() {
|
||||
return format!("(no git repo at {})", proposed.display());
|
||||
}
|
||||
let applied = Coordinator::agent_applied_dir(agent).join("agent.nix");
|
||||
let applied_text = std::fs::read_to_string(&applied).unwrap_or_default();
|
||||
let proposed_text = match git_show(&proposed, commit_ref).await {
|
||||
Ok(s) => s,
|
||||
Err(e) => return format!("(error reading proposed agent.nix: {e:#})"),
|
||||
};
|
||||
unified_diff(&applied_text, &proposed_text)
|
||||
match git_show(&proposed, commit_ref).await {
|
||||
Ok(s) => unified_diff(&applied_text, &s),
|
||||
Err(e) => format!("(error: {e:#})"),
|
||||
}
|
||||
}
|
||||
|
||||
async fn git_show(proposed_dir: &Path, commit_ref: &str) -> Result<String> {
|
||||
|
|
@ -173,7 +181,13 @@ async fn git_show(proposed_dir: &Path, commit_ref: &str) -> Result<String> {
|
|||
.args(["show", &format!("{commit_ref}:agent.nix")])
|
||||
.output()
|
||||
.await
|
||||
.context("git show")?;
|
||||
.with_context(|| {
|
||||
format!(
|
||||
"spawn `git show` in {} (HYPERHIVE_GIT={})",
|
||||
proposed_dir.display(),
|
||||
std::env::var("HYPERHIVE_GIT").unwrap_or_else(|_| "<unset>".into()),
|
||||
)
|
||||
})?;
|
||||
if !out.status.success() {
|
||||
anyhow::bail!(
|
||||
"git show {commit_ref}:agent.nix failed: {}",
|
||||
|
|
|
|||
Loading…
Reference in a new issue