hive-agent: add debug logging around todo upsert/clear/mark-done

diagnostic instrumentation for #2678 (phantom 'you have todos' wakes
after clearing bash-task todos). logs subsystem/key/id/changed on
UpsertTodo, subsystem/key/all/count on ClearTodo, id/count on
MarkTodoDone, and a marker when the serve loop actually consumes a
todo_wake notification. no behavior change - RUST_LOG=debug only.
This commit is contained in:
damocles 2026-07-25 17:40:11 +02:00
commit 2dcbb78b40
2 changed files with 66 additions and 44 deletions

View file

@ -585,7 +585,10 @@ async fn serve_loop<S: Surface>(
}
} {
RecvOutcome::Message(first) => first,
RecvOutcome::LocalTodo => synthetic_todo_message(),
RecvOutcome::LocalTodo => {
tracing::debug!("todo wake consumed, sending synthetic todo message");
synthetic_todo_message()
}
RecvOutcome::Empty => {
// Idle: no message this poll. Service a queued operator
// `/compact` here so it runs even when no turn is driving

View file

@ -165,49 +165,10 @@ fn dispatch(
bus: &Bus,
) -> Response {
match req {
Request::UpsertTodo {
subsystem,
key,
summary,
source,
} => match store.upsert(&subsystem, key.as_deref(), &summary, source.as_deref()) {
Ok((_, changed)) => {
if changed {
wake.notify_one();
}
Response::Ok
}
Err(e) => err(&e),
},
Request::ClearTodo {
subsystem,
key,
all,
} => {
let result = if all {
store.clear_subsystem(&subsystem)
} else {
store.clear(&subsystem, key.as_deref())
};
match result {
Ok(count) => Response::Acked {
count: u64::try_from(count).unwrap_or(0),
},
Err(e) => err(&e),
}
}
Request::ListTodos { subsystem } => match store.list(subsystem.as_deref()) {
Ok(todos) => Response::LooseEnds {
loose_ends: todos.into_iter().map(to_loose_end).collect(),
},
Err(e) => err(&e),
},
Request::MarkTodoDone { id } => match store.mark_done(id) {
Ok(count) => Response::Acked {
count: u64::try_from(count).unwrap_or(0),
},
Err(e) => err(&e),
},
Request::UpsertTodo { .. }
| Request::ClearTodo { .. }
| Request::ListTodos { .. }
| Request::MarkTodoDone { .. } => dispatch_todo(req, store, wake),
Request::StoreReminder {
message,
timing,
@ -260,6 +221,64 @@ fn dispatch(
}
}
/// Todo-family requests (loose-ends v2). `req` is guaranteed by [`dispatch`]
/// to be one of the four todo variants; any other variant is a caller bug.
fn dispatch_todo(req: Request, store: &Todos, wake: &Notify) -> Response {
match req {
Request::UpsertTodo {
subsystem,
key,
summary,
source,
} => match store.upsert(&subsystem, key.as_deref(), &summary, source.as_deref()) {
Ok((id, changed)) => {
tracing::debug!(subsystem = %subsystem, key = ?key, id, changed, "todo upsert");
if changed {
wake.notify_one();
}
Response::Ok
}
Err(e) => err(&e),
},
Request::ClearTodo {
subsystem,
key,
all,
} => {
let result = if all {
store.clear_subsystem(&subsystem)
} else {
store.clear(&subsystem, key.as_deref())
};
match result {
Ok(count) => {
tracing::debug!(subsystem = %subsystem, key = ?key, all, count, "todo clear");
Response::Acked {
count: u64::try_from(count).unwrap_or(0),
}
}
Err(e) => err(&e),
}
}
Request::ListTodos { subsystem } => match store.list(subsystem.as_deref()) {
Ok(todos) => Response::LooseEnds {
loose_ends: todos.into_iter().map(to_loose_end).collect(),
},
Err(e) => err(&e),
},
Request::MarkTodoDone { id } => match store.mark_done(id) {
Ok(count) => {
tracing::debug!(id, count, "todo mark-done");
Response::Acked {
count: u64::try_from(count).unwrap_or(0),
}
}
Err(e) => err(&e),
},
_ => unreachable!("dispatch only routes todo variants here"),
}
}
/// `Request::Compact` handler: gate on context usage, then queue the same
/// deferred `compact_pending` flag the operator's `/compact` button sets.
/// Mirrors `hive-agent::web_ui::actions::post_compact` but reachable from