Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions crates/voro-core/src/agent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1659,7 +1659,7 @@ impl AgentSessionEntry {
}

/// Whether this entry says its session's turn has *ended* — the narrow
/// reading the rest-stop rule acts on (DESIGN.md §8), which is not the same
/// reading the rest rule acts on (DESIGN.md §8), which is not the same
/// question as [`liveness`](Self::liveness). Only `done` answers yes.
/// `blocked` is the case that makes the distinction load-bearing: it reads
/// dead without a live pid, but it is also what a permission prompt and a
Expand Down Expand Up @@ -2348,9 +2348,9 @@ mod tests {
}

/// The built-in `claude` message verb resumes in place (DESIGN.md §8): the
/// supervisor that refuses a headless resume has been released by the
/// rest-stop before any send is made, so the send addresses the session's own
/// reference and the conversation stays under the name Voro composed for it.
/// supervisor that refuses a headless resume is released before the send is
/// made, so the send addresses the session's own reference and the
/// conversation stays under the name Voro composed for it.
#[test]
fn the_builtin_claude_message_verb_resumes_in_place() {
let message = builtin_agents()["claude"].message().unwrap();
Expand Down Expand Up @@ -2663,7 +2663,7 @@ mod tests {
assert_eq!(entry(r#""state": "idle""#), SessionLiveness::Dead);
}

/// Rest is a narrower reading than death (DESIGN.md §8): the rest-stop acts
/// Rest is a narrower reading than death (DESIGN.md §8): the release acts
/// on a turn that has *ended*, and only `done` says so. `blocked` is the
/// separation that matters — dead to the liveness question, yet a turn still
/// under way (a permission prompt, a supervisor mid-turn) that a stop would
Expand Down
34 changes: 15 additions & 19 deletions crates/voro/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -378,8 +378,8 @@ struct CapTarget {
struct MessageTarget {
/// The session row the send updates once it is confirmed — its process, and
/// its reference where the agent's verb forks (DESIGN.md §8). Carried whole
/// rather than as an id, because the inline rest-stop the send may have to
/// make first is addressed at the session itself.
/// rather than as an id, because the release the send may have to make
/// first is addressed at the session itself.
session: voro_core::Session,
/// The reference the send is addressed to: the row's, already established to
/// be present.
Expand Down Expand Up @@ -2514,11 +2514,10 @@ impl App {
}

/// Release the agent's hold on a session, waiting for the answer (DESIGN.md
/// §8), so a headless resume into it can land. The reconciler's rest-stop
/// makes this call off the send path on every pass; the two senders make it
/// inline where that pass cannot have covered them — the quick-message key
/// for the window between an agent handing back and the next pass noticing,
/// the capped-session sweep because no pass will ever release its target.
/// §8), so a headless resume into it can land. Both senders call it inline,
/// immediately before the send it makes deliverable: the quick-message key
/// where the target's listing entry says the agent is holding a session
/// that has come to rest, the capped-session sweep unconditionally.
///
/// Nothing about the row changes either way — the session stays the task's
/// conversation — so a config that will not load costs the release and
Expand Down Expand Up @@ -2622,13 +2621,11 @@ impl App {
));
return;
}
// Normally reconcile has already released a handed-back session
// (DESIGN.md §8), and this finds nothing to do. It fires when the
// operator has outrun a pass — messaging within the same tick the agent
// reported in — and the hold that would refuse an in-place resume is
// still there. Failing to release it refuses the send outright rather
// than spawning one that cannot land, so the task is left exactly where
// it was.
// The agent still holds a session it has finished a turn on, and that
// hold refuses an in-place resume, so it is released here and waited on
// before the send goes out (DESIGN.md §8). Failing to release it
// refuses the send outright rather than spawning one that cannot land,
// so the task is left exactly where it was.
if verdict.at_rest
&& let Err(e) = self.release_session(&target.session)
{
Expand Down Expand Up @@ -7173,11 +7170,10 @@ mod tests {
}
}

/// The inline half of the rest rule (DESIGN.md §8). Normally reconcile has
/// already released a handed-back session and this finds nothing to do; when
/// the operator outruns a pass, the send releases the session itself, at its
/// own reference, and then resumes it in place. Without that the agent's hold
/// would refuse the resume and the feedback would go nowhere.
/// The rest rule (DESIGN.md §8): a session the agent is still holding with
/// its turn ended is released by the send itself, at its own reference, and
/// then resumed in place. Without that the hold would refuse the resume and
/// the feedback would go nowhere.
#[test]
fn a_send_releases_a_session_the_agent_still_holds_at_rest() {
let (mut app, task_id, root, paths) = send_env(FINISHED_LISTING);
Expand Down
32 changes: 14 additions & 18 deletions crates/voro/src/dispatch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1028,11 +1028,9 @@ const MESSAGE_POLL_INTERVAL: Duration = Duration::from_millis(25);
/// How much of a failed send's log to look at for the line to quote back.
const LOG_TAIL_BYTES: u64 = 4096;

/// How long the send path's inline rest-stop is waited on before the send is
/// refused ([`stop_session_now`]). Generous next to the sub-second call it
/// covers, and it is only ever paid when the operator has outrun a reconcile
/// tick — but bounded, because a stop that hangs must not take the cockpit with
/// it.
/// How long the send path's release is waited on before the send is refused
/// ([`stop_session_now`]). Generous next to the sub-second call it covers, but
/// bounded, because a stop that hangs must not take the cockpit with it.
const STOP_WAIT: Duration = Duration::from_secs(5);

/// One line said into a session that already exists (DESIGN.md §8), assembled by
Expand Down Expand Up @@ -1271,11 +1269,10 @@ pub(crate) fn append_launch_log(path: &Path, line: &str) {
}
}

/// Retire an agent's own registry entry for a session Voro has just closed
/// (DESIGN.md §8) — the process half of the rule that a session's entry follows
/// its row. `voro-core` decides *whether* a close should stop (the session comes
/// from `Store::apply_closing` or a reconciler finalisation); this supplies the
/// spawn.
/// Retire an agent's own registry entry for a session an operator's verdict has
/// just closed (DESIGN.md §8) — the process half of the rule that a session's
/// entry follows its row. `voro-core` decides *whether* a close should stop (the
/// session comes back from `Store::apply_closing`); this supplies the spawn.
///
/// Best-effort in every direction, which is what makes it safe to fire from
/// inside a transition that has already committed: an agent that defines no
Expand All @@ -1298,12 +1295,11 @@ pub fn stop_session(ctx: &DispatchCtx, config: &AgentsConfig, session: &Session)
});
}

/// The same stop, waited on: the send path's inline fallback (DESIGN.md §8),
/// where the operator has outrun a reconcile tick and the session is still
/// registered at rest. Unlike the detached form the answer matters — a session
/// whose hold was not released cannot be resumed in place — so this reports
/// rather than merely logging, and the caller refuses the send on an `Err`
/// having committed nothing.
/// The same stop, waited on: the release a send makes of the session it is
/// about to resume (DESIGN.md §8). Unlike the detached form the answer matters
/// — a session whose hold was not released cannot be resumed in place — so
/// this reports rather than merely logging, and the caller refuses the send on
/// an `Err` having committed nothing.
///
/// "Nothing to stop" is `Ok(())`, not a failure: an agent that defines no `stop`
/// verb, or a session with no captured reference, is one Voro was never going to
Expand Down Expand Up @@ -1390,8 +1386,8 @@ fn spawn_stop(

/// Load the agents config for a one-off [`stop_session`], best-effort: a missing
/// or malformed `voro.toml` costs the stop, never the transition that asked for
/// it. Callers that already hold a config — the reconciler, sweeping many
/// sessions — pass their own rather than reloading per session.
/// it. Callers that already hold a config pass their own rather than reloading
/// per session.
pub fn stop_closed_session(ctx: &DispatchCtx, session: &Session) {
if let Ok(config) = AgentsConfig::load(&ctx.agents_path) {
stop_session(ctx, &config, session);
Expand Down
Loading