diff --git a/TODO.md b/TODO.md index 073f74d7..32f21e35 100644 --- a/TODO.md +++ b/TODO.md @@ -31,51 +31,6 @@ - **Privsep the dashboard from the privileged daemon**: hive-c0re runs as root (it has to — `nixos-container` create / start / destroy, the meta git repo, every per-agent bind mount). The HTTP server lives in the same process, so every read-endpoint (`/api/state-file`, `/api/journal/{name}`, `/api/agent-config/{name}`) is one allow-list bug away from serving arbitrary host files. Split the architecture: keep the privileged daemon doing lifecycle + git + ipc, run the web UI as an unprivileged user that talks to the daemon over a unix socket with a narrow request surface (`ReadAgentStateFile { agent, rel_path }` etc.). The unprivileged process can't read `/etc/shadow` even if every check in `get_state_file` is bypassed — it doesn't have the bits. Container-lifecycle POSTs (`/restart`, `/destroy`, etc.) become forwarded RPCs the privileged side authorises on its terms. - **Defense in depth on `get_state_file`**: until privsep lands, the allow-list is load-bearing. Worth adding: refuse files whose mode is not world-readable (so an agent writing a 0600 file inside `state/` can't have its contents proxied through the endpoint to a different operator), and refuse symlinks at any path component (`O_NOFOLLOW`-style — `canonicalize` resolves them, but we currently don't reject if the original path had symlinks). -## Harness Ergonomics (agent-side wishlist) - -Filed by damocles, who actually lives in this thing. Loosely ranked by -how often the friction bites in normal use. - -- ~~**Auto-attach oversize message bodies**~~ — superseded by simply - raising the inline cap from 1 KiB → 4 KiB (covers ~95% of - conversational overflow). Anything genuinely larger still needs a - state file. Blob-in-broker-sqlite was prototyped on paper - (`/agents/damocles/state/oversize-msg-proposal.md`) but rejected as - future vacuum/sync pain not worth carrying for the long-tail 5% of - cases that legitimately belong in a file. -- **Inbox batching hint in the wake prompt** — when the harness pops a - message and there are N more waiting, the wake prompt should say so - (e.g. `"(+3 more queued; consider draining before acting)"`) so claude - knows to call `recv()` again in the same turn instead of doing the - expensive Read/Edit dance once per message over N turns. The data's - already in the broker (`Broker::pending_count(agent)`); just thread it - into the prompt builder in `hive-ag3nt::turn.rs`. Even better: add a - one-shot `recv_batch(max: u32)` MCP tool that returns up to `max` - pending messages in a single round-trip. -- **Self-management of own asks + reminders** — once I fire `ask` or - `remind` I have no way to inspect or cancel them from the agent side. - Operator can cancel asks via dashboard; nothing for reminders at all - (TODO above). Want `list_my_asks() -> [{id, target, question, asked_at}]` - and `cancel_ask(id)` on the agent surface, plus `list_my_reminders()` - / `cancel_reminder(id)`. Bounded by `asker == self` and `reminder.owner - == self` so no cross-agent meddling. -- **`whoami` introspection tool** — agents currently rely on the system - prompt remembering their name + role. After a rename or model swap - there's no trustworthy source-of-truth from inside the harness. - Cheap: a `whoami() -> { name, role: "agent" | "manager", model, port, - hyperhive_rev, started_at }` tool reading from the harness's own env - + `TurnState`. Useful for self-documenting state files ("this dropped - by damocles@gpt-5-codex on rev abc1234") and for the future - `get_open_threads` to know whose threads to query without - trusting prompt-substituted strings. -- **Optional `in_reply_to: ` on send** — pure wire addition; no - behavioural change. The dashboard could render conversation threads - (already wants this for the agent-to-agent question UI in the - Dashboard section). Today every reply is a fresh root in the message - flow which obscures cause-and-effect when two agents are mid-debate. - Field is optional, ignored if the referenced id is unknown / cross- - agent / out of retention. - ## Bugs - **Post-rebuild system-message missed wake**: at 09:13:14 the dashboard showed `system → damocles container rebuilt` as ✓ delivered, but the agent harness never ran a turn for it (no claude invocation, no operator-visible activity). A subsequent `recv()` from inside the agent returned `(empty)`, confirming the message was popped + marked delivered server-side — yet drove no turn. Most likely cause: the agent_server `serve_agent_stdio` task is up and answering MCP/socket calls, but the `hive-ag3nt::serve` long-poll loop that drives `drive_turn` either died silently during rebuild or never restarted. Investigate: (a) does hive-ag3nt's serve loop survive `nixos-container update` cleanly, or does its tokio runtime get torn down mid-loop? (b) is there an early-exit path on a transient socket error during rebuild that drops the serve task without notifying the manager? (c) compare timeline with manager's own post-rebuild wake to see if this is rebuilt-agents-only or universal. Could be related to the `recv_blocking` fix in `e423d57` if the rebuild restarts the broker mid-subscribe. diff --git a/hive-ag3nt/src/mcp.rs b/hive-ag3nt/src/mcp.rs index 7c470e00..51e1066c 100644 --- a/hive-ag3nt/src/mcp.rs +++ b/hive-ag3nt/src/mcp.rs @@ -152,7 +152,7 @@ pub struct RecvArgs { #[derive(Debug, serde::Deserialize, schemars::JsonSchema)] pub struct RemindArgs { /// Body that lands in your inbox when the reminder fires (sender - /// will appear as `reminder`). Soft cap at 4 KiB inline — anything + /// will appear as `reminder`). Soft cap at 1 KiB inline — anything /// larger gets auto-persisted to a file under /// `/agents//state/reminders/auto-.md` and the inbox /// message becomes a short pointer. Pass `file_path` if you want @@ -322,7 +322,7 @@ impl AgentServer { time (sender will appear as `reminder`). Use for self-paced follow-ups: 'check task \ status in 60s', 'retry failed deploy at 14:00 UTC', 'nudge me when the operator's \ deploy window opens'. Set EXACTLY ONE of `delay_seconds` (fire N seconds from now) \ - or `at_unix_timestamp` (fire at absolute epoch second). Body soft-caps at 4 KiB \ + or `at_unix_timestamp` (fire at absolute epoch second). Body soft-caps at 1 KiB \ inline — anything larger gets auto-persisted to a file under your \ `/agents//state/reminders/` dir and the inbox message becomes a short pointer; \ pass `file_path` if you want to control the destination yourself. Returns \ @@ -455,7 +455,7 @@ pub struct AnswerArgs { /// Id of the question being answered — comes from the /// `question_asked` event in your inbox. pub id: i64, - /// Free-text answer body. Soft-capped at 4 KiB by the same + /// Free-text answer body. Soft-capped at 1 KiB by the same /// `MESSAGE_MAX_BYTES` limit as `send`; keep it short or write the /// detail to a file and pass a path. pub answer: String, @@ -750,7 +750,7 @@ impl ManagerServer { time (sender will appear as `reminder`). Use for self-paced manager follow-ups: \ 'recheck pending approval in 10m', 'nudge alice if she hasn't replied by 14:00 \ UTC'. Set EXACTLY ONE of `delay_seconds` (fire N seconds from now) or \ - `at_unix_timestamp` (fire at absolute epoch second). Body soft-caps at 4 KiB \ + `at_unix_timestamp` (fire at absolute epoch second). Body soft-caps at 1 KiB \ inline — anything larger gets auto-persisted to a file under `/state/reminders/` \ (the manager's own state mount) and the inbox message becomes a short pointer. \ Pass `file_path` if you want to control the destination yourself." diff --git a/hive-c0re/src/agent_server.rs b/hive-c0re/src/agent_server.rs index ef03d3ea..e9dfffd2 100644 --- a/hive-c0re/src/agent_server.rs +++ b/hive-c0re/src/agent_server.rs @@ -178,7 +178,7 @@ async fn dispatch(req: &AgentRequest, agent: &str, coord: &Arc) -> } /// Common Send handler shared between dispatch arms. Applies the -/// 4 KiB body cap, then routes broadcast (`to == "*"`) vs unicast +/// 1 KiB body cap, then routes broadcast (`to == "*"`) vs unicast /// through their respective broker calls. Pulled out of `dispatch` /// to keep that function under the clippy too-many-lines limit; the /// behaviour is identical to inlining. diff --git a/hive-c0re/src/limits.rs b/hive-c0re/src/limits.rs index a1e3a173..43d921cc 100644 --- a/hive-c0re/src/limits.rs +++ b/hive-c0re/src/limits.rs @@ -11,15 +11,10 @@ //! transparently and the inbox sees a pointer. /// Per-message body cap. Applies to `send`, `ask` question text, -/// `answer` body, and the stored inline form of a reminder. 4 KiB -/// catches the bulk of conversational overflow (status reports, -/// bullet-list summaries, short proposals) while staying small -/// enough that a backed-up inbox of ~10 unread messages only adds -/// ~40 KiB to the recipient's wake-prompt context. Genuinely -/// long-form artifacts (audit reports, full diffs, transcripts) -/// still belong in a state file — the error message on overflow -/// points callers at that escape hatch. -pub const MESSAGE_MAX_BYTES: usize = 4096; +/// `answer` body, and the stored inline form of a reminder. 1 KiB +/// is small enough that 100 unread messages don't dominate a wake +/// prompt, large enough for routine cross-agent chatter. +pub const MESSAGE_MAX_BYTES: usize = 1024; /// Validate that `body` fits under [`MESSAGE_MAX_BYTES`]. Returns a /// caller-ready error string (caller wraps in diff --git a/hive-sh4re/src/lib.rs b/hive-sh4re/src/lib.rs index 6e0552d9..f76d73ab 100644 --- a/hive-sh4re/src/lib.rs +++ b/hive-sh4re/src/lib.rs @@ -532,7 +532,7 @@ pub enum ManagerRequest { }, /// Mirror of `AgentRequest::Remind` on the manager surface — schedule /// a reminder addressed to the manager itself. Same semantics: body - /// soft-caps at 4 KiB, oversize bodies auto-persist to + /// soft-caps at 1 KiB, oversize bodies auto-persist to /// `/state/reminders/auto-.md` (the manager container's own state /// mount) and the inbox sees a pointer. Remind {