hive-sh4re + docs: extract LooseEnd wire-shape prose (#717 batch 4)
This commit is contained in:
parent
a0b15ed6a4
commit
22b3e542c5
2 changed files with 69 additions and 51 deletions
|
|
@ -91,6 +91,58 @@ each id in a per-recipient in-memory set so the next `Recv` can tag
|
|||
the row with `redelivered: true`. Idempotent + cheap when there's
|
||||
nothing in flight, so the at-boot fire is unconditional.
|
||||
|
||||
### Loose-ends wire shape
|
||||
|
||||
`LooseEnd` is the per-row response shape for `GetLooseEnds` (both
|
||||
the agent-flavour and manager-flavour requests). Tagged enum so
|
||||
new thread kinds (forge PRs, long-running approvals from a
|
||||
privileged bot, etc.) can land later without breaking existing
|
||||
handlers. Each row carries enough context that the caller renders
|
||||
it directly as a bulleted list, no follow-up fetch needed.
|
||||
|
||||
Per-flavour scoping is uniform across the three variants:
|
||||
|
||||
- **agent-flavour** `GetLooseEnds` only surfaces rows the calling
|
||||
agent has standing in. `Approval` rows only appear when the
|
||||
calling agent is the manager (sub-agents don't submit
|
||||
approvals). `Question` rows surface where the agent is `asker`
|
||||
OR `target` (the routing semantics from the Ask/Answer
|
||||
subsection above). `Reminder` rows are scoped to `owner ==
|
||||
self`.
|
||||
- **manager-flavour** `GetLooseEnds` lists every pending row in
|
||||
the swarm — full audit view.
|
||||
|
||||
Per-variant fields:
|
||||
|
||||
- `Approval { id, agent, commit_ref, description?, age_seconds }`
|
||||
— `agent` is the affected agent (target of the spawn / config
|
||||
commit), not the asker. `description` is the manager's free-text
|
||||
blurb shown on the dashboard card. `commit_ref` is the
|
||||
kind-specific payload (see `docs/approvals.md::Approval kinds
|
||||
(wire shapes)`).
|
||||
- `Question { id, asker, target?, question, age_seconds }` —
|
||||
`target = None` = operator-routed (dashboard); `Some(agent)` =
|
||||
peer-to-peer thread.
|
||||
- `Reminder { id, owner, message, due_at, age_seconds }` —
|
||||
`due_at` is the absolute unix timestamp the scheduler is
|
||||
targeting; clients compute time-until-fire as `due_at - now`.
|
||||
|
||||
`age_seconds` saturates at zero on any clock anomaly (back-step,
|
||||
unsynchronised wall clock, etc.) so the bulleted list never
|
||||
shows nonsense ages.
|
||||
|
||||
`CancelLooseEnd { kind, id }` is the matching write surface. The
|
||||
`kind` enum (`Question` / `Reminder` / `Approval`) selects which
|
||||
underlying store the dispatcher reaches into. `Question` and
|
||||
`Reminder` cancel from either surface subject to ownership
|
||||
checks (asker for the question, scheduler for the reminder).
|
||||
`Approval` is manager-only — sub-agents don't submit approvals
|
||||
so they have nothing of their own to withdraw; their wire
|
||||
surface returns a clear error if they try. Cancelling an approval
|
||||
transitions the row to `ApprovalStatus::Cancelled` and fires
|
||||
`ApprovalResolved { status: "cancelled" }` so the dashboard pulls
|
||||
the card out of the pending pane.
|
||||
|
||||
## Async forms
|
||||
|
||||
Dashboard + per-agent mutating forms carry `data-async`; a delegated
|
||||
|
|
|
|||
|
|
@ -249,79 +249,50 @@ pub enum ReminderTiming {
|
|||
}
|
||||
|
||||
/// One row in the response to `GetLooseEnds`. Tagged enum so new
|
||||
/// thread kinds (forge PRs, long-running approvals from a privileged
|
||||
/// bot, etc) can land later without breaking existing handlers. The
|
||||
/// caller (claude in the agent harness) is expected to render these
|
||||
/// as a short bulleted list — the per-row fields are all the context
|
||||
/// needed without a follow-up fetch.
|
||||
///
|
||||
/// All three variants are cancellable via `CancelLooseEnd` /
|
||||
/// `cancel_loose_end`. `Question` and `Reminder` can be cancelled
|
||||
/// from either surface (subject to ownership checks); `Approval`
|
||||
/// is manager-only since sub-agents can't submit approvals.
|
||||
/// thread kinds can land without breaking existing handlers.
|
||||
/// Per-flavour scoping + per-variant fields + clock-anomaly
|
||||
/// saturation behaviour live in
|
||||
/// `docs/conventions.md::Loose-ends wire shape`.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(tag = "kind", rename_all = "snake_case")]
|
||||
pub enum LooseEnd {
|
||||
/// A pending approval. For agent-flavour `GetLooseEnds` calls
|
||||
/// this only surfaces when the agent itself is the manager
|
||||
/// (sub-agents don't submit approvals). For manager-flavour calls
|
||||
/// it lists every pending approval in the swarm. `agent` is the
|
||||
/// affected agent (target of the spawn / config commit).
|
||||
/// A pending approval row.
|
||||
Approval {
|
||||
id: i64,
|
||||
agent: String,
|
||||
commit_ref: String,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
description: Option<String>,
|
||||
/// Wall-clock seconds since `requested_at`. Saturates at zero on
|
||||
/// any clock anomaly (back-step etc).
|
||||
age_seconds: u64,
|
||||
},
|
||||
/// An unanswered question. For agent-flavour calls: only threads
|
||||
/// where the agent is `asker` OR `target`. For manager-flavour
|
||||
/// calls: every unanswered question in the swarm. `target = None`
|
||||
/// means the question is addressed to the operator (dashboard
|
||||
/// path); `Some(agent)` is a peer-to-peer thread.
|
||||
/// An unanswered question row.
|
||||
Question {
|
||||
id: i64,
|
||||
asker: String,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
target: Option<String>,
|
||||
question: String,
|
||||
/// Wall-clock seconds since `asked_at`. Saturates at zero.
|
||||
age_seconds: u64,
|
||||
},
|
||||
/// A scheduled but un-delivered reminder. For agent-flavour calls:
|
||||
/// only the agent's own reminders (where `owner == self`). For
|
||||
/// manager-flavour calls: every pending reminder in the swarm.
|
||||
/// `owner` is the agent who scheduled it; `due_at` is the absolute
|
||||
/// unix timestamp the scheduler is targeting.
|
||||
/// A scheduled but un-delivered reminder row.
|
||||
Reminder {
|
||||
id: i64,
|
||||
owner: String,
|
||||
message: String,
|
||||
due_at: i64,
|
||||
/// Wall-clock seconds since the reminder was scheduled. Saturates
|
||||
/// at zero on clock anomalies. (For time-until-fire, compute
|
||||
/// `due_at - now` client-side from the wire timestamp.)
|
||||
age_seconds: u64,
|
||||
},
|
||||
}
|
||||
|
||||
/// Kind discriminator for `CancelLooseEnd`. Maps to which underlying
|
||||
/// store the dispatcher reaches into (`OperatorQuestions` /
|
||||
/// `Broker::reminders` / `Approvals`). The `Approval` variant is
|
||||
/// manager-only — sub-agents can't submit approvals so they have
|
||||
/// nothing to withdraw (closes #250).
|
||||
/// Kind discriminator for `CancelLooseEnd`. Per-kind store +
|
||||
/// authorisation rules live in
|
||||
/// `docs/conventions.md::Loose-ends wire shape`.
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum CancelLooseEndKind {
|
||||
Question,
|
||||
Reminder,
|
||||
/// Withdraw a pending approval (manager surface only). The row
|
||||
/// transitions to `ApprovalStatus::Cancelled` and an
|
||||
/// `ApprovalResolved` event fires so the dashboard pulls the card
|
||||
/// out of the pending pane.
|
||||
/// Withdraw a pending approval (manager surface only).
|
||||
Approval,
|
||||
}
|
||||
|
||||
|
|
@ -413,11 +384,9 @@ pub enum AgentRequest {
|
|||
#[serde(default)]
|
||||
file_path: Option<String>,
|
||||
},
|
||||
/// Loose-ends view: pending approvals + unanswered questions
|
||||
/// pending against THIS agent. Approvals only surface if this
|
||||
/// agent submitted them (which only ever happens for the
|
||||
/// manager); questions surface where the agent is `asker` or
|
||||
/// `target`. Cheap O(n) sweep server-side — no caching.
|
||||
/// Loose-ends view: every pending row against THIS agent.
|
||||
/// Per-flavour scoping in
|
||||
/// `docs/conventions.md::Loose-ends wire shape`.
|
||||
GetLooseEnds,
|
||||
/// Count of this agent's pending (un-delivered) reminders. Used
|
||||
/// by the harness's per-turn stats sink to snapshot "what was
|
||||
|
|
@ -450,12 +419,9 @@ pub enum AgentRequest {
|
|||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
name: Option<String>,
|
||||
},
|
||||
/// Cancel an open thread the agent owns: a `Question` they asked
|
||||
/// (returns `[cancelled by <self>]` as the answer to the asker)
|
||||
/// or a `Reminder` they scheduled (hard-deletes the row).
|
||||
/// Authorisation on the sub-agent surface: caller must own the
|
||||
/// row. The manager surface uses the same wire variant but
|
||||
/// accepts any id.
|
||||
/// Cancel an open thread the agent owns. Authorisation +
|
||||
/// per-kind semantics in
|
||||
/// `docs/conventions.md::Loose-ends wire shape`.
|
||||
CancelLooseEnd { kind: CancelLooseEndKind, id: i64 },
|
||||
/// Mark every message popped since the last `AckTurn` as handled.
|
||||
/// Harness↔broker pairing fired after `TurnOutcome::Ok`. See
|
||||
|
|
|
|||
Loading…
Reference in a new issue