Skip to content

Escalate before idle: a ladder between "decide myself" and "ask the human" #100

Open

Description

@androidand
Proposal

Parent: autonomous-swarm (F2).

Why

Agents stop and hand to the operator with sentences like "Peer X owns blocker Y, so I
cannot continue until that is solved". The operator then talks to every session by hand.
Causes found in code:

  • send-peer-message.txt ends: "If you cannot continue without an answer, say that to your
    own user rather than asking the peer again." With "send once", "never ask whether a peer
    is done" and "do not reply to a reply", the safe reading is: one message, then stop and
    tell the human. Those rules exist for a real storm (hundreds of identical messages,
    2026-09-18) and over-corrected.
  • The loop recognises empty output, plan-without-tools and repetition
    (loop/continuation.ts). It has no concept of a turn that ended asking a question or
    waiting on a peer, so such a turn ends the run or burns the no-progress budget.
  • .skein/blocker.md quarantines a stuck change while the queue continues, but nothing
    tells a model to use it for "waiting on a peer", and nothing re-checks it when the peer
    answers.

What Changes

  1. The ladder (design D3 of the epic), stated in the brief charter and the tool text:
    decide from specs → ask the owner peer once with a deadline and continue other work →
    ask the lead once after the deadline → record a blocker tagged needs: team|human and
    move on → only needs: human reaches the human, as a board item.
  2. Blocker records get structure: .skein/blocker.md gains front matter
    (needs, waiting-on, asked-at, deadline, rung), parsed with a closed schema, so
    the driver can enforce rung order and wake the item when the peer replies.
  3. Wake, don't poll: when a member is blocked on a peer it registers a wake on that
    peer's reply or idle (the existing PeerInbox drain-on-idle and reply correlation),
    plus a durable deadline timer in the loop. No "are you done?" messages.
  4. Stop classifier in the loop: a turn that ends with no tool calls and text matching
    "asking the user / waiting on a peer / cannot continue until" is classified
    blocked-on-human-or-peer and answered with a bounded ladder nudge ("do not stop to ask:
    apply rung N") instead of ending the run. Pure, import-free module like
    loop/continuation.ts. It only ever produces a nudge, never an action.
  5. Tool text and charter corrected so "say that to your user" is the last rung, not the
    first, while keeping every anti-storm rule: one message per (item, rung), repeat guard
    intact.
  6. Plain (non-loop) sessions get the same ladder through the instruction fragment, and
    the lead frame from lead-authority already says "reply to the lead; do not ask your
    user first".

Wake sources and the deadline timer are simplest inside a project server (one in-process
inbox, durable loop records) and degrade to the per-process versions otherwise.

Non-goals

  • No automatic answer on the human's behalf for needs: human items (goal ambiguity,
    credentials, irreversible or never-listed actions).
  • No new message transport; no polling.

Impact

loop/continuation.ts (+ new loop/stop-reason.ts), loop/loop.ts iteration handling,
loop/spec-queue/* (blocker schema, wake), tool/send-peer-message.txt,
session/peers.ts charter text, peer/inbox.ts wake hook, tests.

Tasks

Phase 0: Corpus and baseline

  • 0.1 Collect the stop corpus from autonomous-swarm 0.2 (real transcripts, labelled).
    Include negatives: turns that end legitimately with a summary and no question.
  • 0.2 Failing test: a turn ending "I'll wait for peer X to resolve Y" is today treated as
    neither stalled-for-a-reason nor continued (records current behaviour).

Phase 1: Blocker schema and ladder state

  • 1.1 Front-matter schema for .skein/blocker.md (needs, waiting-on, asked-at,
    deadline, rung), closed, parsed at load; legacy free-text blockers still read as
    needs: team.
  • 1.2 Observed-red tests: unknown key, needs neither team nor human, rung out of order.
  • 1.3 Rung enforcement: at most one send per (item, rung), with a positive control that
    an identical second condition does not resend.

Phase 2: Stop classifier

  • 2.1 loop/stop-reason.ts: pure classifier (asking-user / waiting-on-peer / done /
    other), no imports from loop.ts (cycle rule in similarity.ts).
  • 2.2 Wire to iteration handling: classified stop → ladder nudge, bounded (3 per item),
    then record a needs: team blocker and continue the queue.
  • 2.3 Replay the corpus: report precision/recall and false nudges per hour. Ship only if
    false nudges are tolerable (decide threshold with the operator).

Phase 3: Wake instead of poll

  • 3.1 Register wake on peer reply/idle via PeerInbox; deadline timer persisted with the
    loop record so a restart keeps it.
  • 3.2 On wake, re-check the blocker and resume the item; on deadline, perform rung L2
    (one request to the lead) exactly once.

Phase 4: Text

  • 4.1 Rewrite the relevant paragraph of send-peer-message.txt; keep the storm
    protections verbatim.
  • 4.2 Add the ladder to the crew charter (crew-loop 3.2) and the Claude Code fragment.

Phase 5: Live

  • 5.1 Two members, deliberate cross-dependency: A blocked on B's change. Success: A moves
    to another item, B's reply wakes A, the human is not contacted.
  • 5.2 Negative control: needs: human blocker (credentials) reaches the board and the
    lead brief and does not stop the member.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions