ask_operator: any agent can call it, answer routes by asker

new AgentRequest::AskOperator + AgentResponse::QuestionQueued on
the per-agent socket — same shape as the manager flavor, agent
gets the same wire surface (still uses the same operator_questions
table). agent_server::dispatch wires AskOperator through coord
.questions.submit(agent, ...) so the row's asker is the sub-agent
name; the ttl watchdog already in manager_server gets shared and
spawn_question_watchdog goes pub.

answer routing: operator_questions::answer now returns (question,
asker). post_answer_question + post_cancel_question + the watchdog
fire OperatorAnswered through new coord.notify_agent(asker, event)
instead of always notify_manager — the event lands in whichever
agent originally asked. notify_manager is now a thin wrapper.

agent socket plumbing: agent_server::start takes Arc<Coordinator>
instead of Arc<Broker> so dispatch has access to questions +
notify path; coordinator::{register_agent,ensure_runtime} take
self: &Arc<Self>. mcp::AgentServer grows the ask_operator tool;
allowed_mcp_tools(Agent) adds it; prompts/agent.md replaces the
'message the manager to ask the operator' guidance with the
direct tool description.
This commit is contained in:
müde 2026-05-16 01:48:10 +02:00
parent 6b3ef4549c
commit 2a6d084718
9 changed files with 156 additions and 43 deletions

View file

@ -117,17 +117,20 @@ impl OperatorQuestions {
}
/// Mark the question answered. Returns the original question text so the
/// caller can include it in any helper event it fires off.
pub fn answer(&self, id: i64, answer: &str) -> Result<String> {
/// Mark a pending question answered. Returns `(question, asker)`
/// so the caller can both echo the question back in a helper
/// event AND route that event to whichever agent originally
/// asked it.
pub fn answer(&self, id: i64, answer: &str) -> Result<(String, String)> {
let conn = self.conn.lock().unwrap();
let question: Option<(String, Option<i64>)> = conn
let row: Option<(String, String, Option<i64>)> = conn
.query_row(
"SELECT question, answered_at FROM operator_questions WHERE id = ?1",
"SELECT question, asker, answered_at FROM operator_questions WHERE id = ?1",
params![id],
|row| Ok((row.get(0)?, row.get(1)?)),
|row| Ok((row.get(0)?, row.get(1)?, row.get(2)?)),
)
.optional()?;
let Some((question, answered_at)) = question else {
let Some((question, asker, answered_at)) = row else {
bail!("question {id} not found");
};
if answered_at.is_some() {
@ -137,7 +140,7 @@ impl OperatorQuestions {
"UPDATE operator_questions SET answer = ?1, answered_at = ?2 WHERE id = ?3",
params![answer, now_unix(), id],
)?;
Ok(question)
Ok((question, asker))
}
#[allow(dead_code)]