generated from coulomb/repo-seed
feat(WARDEN-WP-0020): worker drafts real route answers in dry-run (T3 groundwork)
build_plans now computes the concrete routing answer for each route_answer action in-process (reuses the catalog; read-only, no subprocess/network) and render_plans shows it as a `draft:` line. The dry-run demonstrates the actual answer the executor (T3) will send, not just an intent. RuleBrain stays the default; the llm-connect brain (T2) is gated on llm-connect being operational + its /execute contract. 230 tests, lint clean. Live dry-run verified against the real inbox. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -149,9 +149,43 @@ class HubClient:
|
||||
return data if isinstance(data, list) else []
|
||||
|
||||
|
||||
def draft_route_answer(query: str) -> str:
|
||||
"""Compute the routing answer the worker would send for a query. Read-only.
|
||||
|
||||
Reuses the routing catalog in-process (no subprocess, no network) so the dry-run
|
||||
shows the concrete answer the executor (T3) will send, not just an intent.
|
||||
"""
|
||||
try:
|
||||
from warden.routing.catalog import load_catalog
|
||||
|
||||
matches = load_catalog().find(query, limit=1)
|
||||
except Exception: # noqa: BLE001 — never let a lookup failure break planning
|
||||
return ""
|
||||
if not matches:
|
||||
return f"No routing match for {query!r}; try `warden route list --all`."
|
||||
e = matches[0]
|
||||
role = "issue" if e.warden_executes else ("assist" if e.exec_capable else "route")
|
||||
parts = [f"{e.id} — owner {e.owner_repo} ({e.subsystem}), warden role: {role}."]
|
||||
if e.warden_executes and e.cert_command:
|
||||
parts.append(f"Run: {e.cert_command}.")
|
||||
elif e.has_native_exec:
|
||||
parts.append(f"Primary: {e.exec_command}.")
|
||||
elif e.exec_capable:
|
||||
parts.append(f"Proxy: warden access {e.id} --fetch (as the caller).")
|
||||
parts.append(f"See {e.wiki_ref}.")
|
||||
return " ".join(parts)
|
||||
|
||||
|
||||
def build_plans(messages: List[dict], brain: Brain) -> List[WorkerPlan]:
|
||||
"""Plan every message and apply the guardrail pass. Pure — no execution."""
|
||||
return [_guardrail(brain.plan(m), m) for m in messages]
|
||||
"""Plan every message, attach computed route answers, and apply the guardrail pass."""
|
||||
plans: List[WorkerPlan] = []
|
||||
for m in messages:
|
||||
plan = brain.plan(m)
|
||||
for a in plan.actions:
|
||||
if a.kind == "route_answer" and "answer" not in a.payload:
|
||||
a.payload["answer"] = draft_route_answer(a.payload.get("query", m.get("subject", "")))
|
||||
plans.append(_guardrail(plan, m))
|
||||
return plans
|
||||
|
||||
|
||||
def render_plans(plans: List[WorkerPlan]) -> str:
|
||||
@@ -167,6 +201,8 @@ def render_plans(plans: List[WorkerPlan]) -> str:
|
||||
for a in p.actions:
|
||||
mark = "→" if a.risk == "safe" else "⚠"
|
||||
lines.append(f" {mark} {a.kind}: {a.summary}")
|
||||
if a.payload.get("answer"):
|
||||
lines.append(f" draft: {a.payload['answer']}")
|
||||
if a.risk == "escalate":
|
||||
lines.append(f" escalated: {a.reason}")
|
||||
return "\n".join(lines)
|
||||
|
||||
Reference in New Issue
Block a user