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
2 changes: 1 addition & 1 deletion .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
{
"name": "doperpowers",
"description": "Emerges by humans, Converges by Agent",
"version": "7.175.0",
"version": "7.176.0",
"source": "./",
"author": {
"name": "SSFSKIM",
Expand Down
2 changes: 1 addition & 1 deletion .claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "doperpowers",
"description": "Emerges by humans, Converges by Agents",
"version": "7.175.0",
"version": "7.176.0",
"author": {
"name": "SSFSKIM",
"email": "supremekim17@gmail.com"
Expand Down
2 changes: 1 addition & 1 deletion .codex-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "doperpowers",
"version": "7.175.0",
"version": "7.176.0",
"description": "One software-development methodology for coding agents, sized by the work: brainstorm, design, spec, TDD, review, ship — one spec that carries its plan of work as milestones: one milestone runs in order from the spec, several run a fresh subagent per milestone reviewed at each dependency frontier, and the same method runs unattended on the board loop.",
"author": {
"name": "SSFSKIM",
Expand Down
13 changes: 13 additions & 0 deletions docs/doperpowers/TECH-DEBT.md
Original file line number Diff line number Diff line change
Expand Up @@ -429,3 +429,16 @@ the spec's Decision Log entry "the branch review round"
Accepted at design (Decision Log, round 3). Trigger: a raw value found in
the session store's copy of a `tool-results/` file, or the store gaining a
reader other than the person.

## sminos bridge: a delivery wakes a stopped session (2026-10-17, e269230e)

- **A late stop drop can be lost with no refusal when its post fails.** A
delivery the engine drops for a stop after the mod answered `submitted` goes
to the child as a keyed `inbox` event. If the post carrying it fails before
the child takes it in, the poster replaces the batch with a gap and a resync
(bridge-stream.tsx), and neither carries inbox work. The message is then
lost, its mark stays, and the daemon is never told it `failed`. Two things
must happen together: a prompt queued in the instant the session turned
busy, then an Esc; and a failed post to the child's local socket. Trigger:
a delivery reported lost with no refusal, or the poster's resync gaining
retained work for another reason.
7 changes: 4 additions & 3 deletions docs/doperpowers/plans/2026-10-15-arkho-desk-c2-bridge.md

Large diffs are not rendered by default.

8 changes: 8 additions & 0 deletions hooks/mods/bridge-rows.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,14 @@ function remember(loop: string, uuid: string) {
*/
export function compacted(agentId: string | null, messages: readonly SessionMessage[] | undefined) {
const loop = agentId ?? 'main'
// The main conversation compacted holds its rows in a summary, not
// verbatim, so the delivered rows still tracked (bridge.tsx,
// recheckDeliveries) would all read as missing and be given twice: every
// one is let go. A row a rewind left on a dead branch before the
// compaction is the accepted loss.
if (agentId === null) {
state.deliveredRows = []
}
const after = state.cut.get(loop)
const summary = messages?.[0]?.handle
// With no cut yet (a module that has not read its first hello) there is
Expand Down
26 changes: 23 additions & 3 deletions hooks/mods/bridge-state.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,20 @@ export type Call = {
question?: unknown
}
/**
* A steer, bound to the main turn it was given in before its row is appended.
* A steer (or a delivery given into the turn), bound to the main turn it was
* given in before its row is appended.
* `isAppended`: the row is in; `isRead`: a main-loop request began after it;
* `ended`: how its turn ended, while some append of that turn is still pending.
* `ended`: how its turn ended, while some append of that turn is still pending;
* `isDelivery`: a delivery's row, which an interrupted turn does not leave unread.
*/
export type Steer = { turnId: string; isAppended: boolean; isRead: boolean; ended?: string }
export type Steer = { turnId: string; isAppended: boolean; isRead: boolean; ended?: string; isDelivery?: boolean }
/**
* The wake a turn owes for delivered rows it answered before reading, until
* its prompt settles: `isRead`, a main-loop request began since that carries
* them; `turnAt`, the main turn already running when it was given, whose
* first request was built before the rows went in.
*/
export type Wake = { isRead: boolean; turnAt?: string }
/** What an interrupt did to the Bash calls it moved to the background: the tasks it stopped, and those it could not. */
export type Stops = { stopped: string[]; running: string[] }
/** An interrupt of a main turn: `recorded` settles once its record is in (or it failed); the turn's Bash calls wait for it. */
Expand Down Expand Up @@ -100,6 +109,11 @@ export type ListedAgent = { id: string; description: string; type: string; statu
export type DeliveryKey = { project?: string; writer: string; seq: number }
/** A log event submitted as the human's words, whose row is still to be kept. */
export type Submission = { key: DeliveryKey; text: string }
/**
* A delivery the bridge put in the conversation as a row, until the
* conversation is known to keep it: `turnId`, the main turn it was appended in.
*/
export type DeliveredRow = { key: DeliveryKey; text: string; turnId: string }
/** A delivery the engine dropped after it was submitted, as `delivered {outcome: "refused"}` reports it. */
export type Refusal = { key: DeliveryKey; reason: string }

Expand Down Expand Up @@ -175,6 +189,8 @@ export type BridgeState = {
isSeeded: boolean
/** Steers given in the main turn, and those whose turn ended before their row was in. */
steers: Steer[]
/** Wakes for unread delivered rows whose prompt has not settled (bridge.tsx, wakeForSteer). */
wakes: Wake[]
/** The main turns the bridge's interrupt is ending, by turn, until each one ends. */
interrupts: Map<string, Interrupt>
/** For each running foreground subagent, the main turn it belongs to (its tool calls are that turn's). */
Expand Down Expand Up @@ -241,6 +257,8 @@ export type BridgeState = {
agentListsInFlight: Set<Promise<readonly ListedAgent[]>>
/** Deliveries submitted whose prompt row has not been kept yet, oldest first (`delivered` `entered`). */
submissions: Submission[]
/** Delivered rows not yet known to stay in the conversation the next request reads (bridge.tsx, recheckDeliveries). */
deliveredRows: DeliveredRow[]
/**
* Refusals no post the child answered has carried yet, oldest first: every
* resync carries them, so one whose post was lost still reaches the child.
Expand Down Expand Up @@ -273,6 +291,7 @@ const fresh = (): BridgeState => ({
conversation: 0,
agentListsInFlight: new Set(),
submissions: [],
deliveredRows: [],
refusals: [],
generation: 0,
respawnMs: RESPAWN_FIRST_MS,
Expand All @@ -290,6 +309,7 @@ const fresh = (): BridgeState => ({
delivered: new Map(),
isSeeded: false,
steers: [],
wakes: [],
interrupts: new Map(),
foreground: new Map(),
})
Expand Down
5 changes: 5 additions & 0 deletions hooks/mods/bridge-stream.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,11 @@ export function forgetLive() {
// Its agents were set aside first (bridge-agents.tsx, forgetAgents).
state.agents.clear()
state.submissions = []
state.deliveredRows = []
// A steer or delivery whose append settles after the /clear, and a wake
// still pending, are the old conversation's: none wakes the new one.
state.steers = []
state.wakes = []
state.refusals = []
state.delivered.clear()
state.loopTurns.clear()
Expand Down
Loading
Loading