Skip to content

Commit 0cba9a4

Browse files
hyperpolymathclaude
andcommitted
feat(cartridge): add consultant_qa tool for Phase 6 echidnabot Q&A
The 16th tool on the echidna-llm-mcp cartridge. Composes existing ECHIDNA endpoints (/api/search + /api/tactics/suggest) into a single markdown Q&A response. Used by echidnabot when a Consultant-mode repo gets an `@echidnabot` mention on a PR comment. Why composition not a new endpoint: - ECHIDNA already exposes corpus search (66,674-proof corpus) and aspect-tag tactic suggestions - A free-form Q&A endpoint at /api/consult is one design path, but composition delivers shippable value today without an upstream ECHIDNA change - When /api/consult lands, this cartridge tool can route there preferentially without breaking existing callers Args: { repo, pr_number, question, context } Returns: { answer: markdown, provenance: "search+tactics composite" } The empty-question case returns a polite "ping me with a question" note rather than firing the LLM round-trip. Pairs with echidnabot commit 4a7871c (Phase 6 / Bit 6b — BoJ-mediated Q&A handler). When BoJ revives, echidnabot's `query_boj_q_and_a` will hit this tool through `/cartridge/echidna-llm-mcp/invoke` and the sunset condition on echidnabot's [exceptions.boj-only-mcp] block becomes evaluable. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 9e49a5a commit 0cba9a4

2 files changed

Lines changed: 114 additions & 0 deletions

File tree

cartridges/echidna-llm-mcp/cartridge.json

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,32 @@
259259
},
260260
"required": ["session_id"]
261261
}
262+
},
263+
{
264+
"name": "consultant_qa",
265+
"description": "Compose a markdown Q&A response for echidnabot's Consultant mode. Pulls related corpus precedent (/api/search) and tactic hints (/api/tactics/suggest), formats both into a single markdown answer block. Used when a Consultant-mode repo gets an `@echidnabot` mention on a PR comment.",
266+
"inputSchema": {
267+
"type": "object",
268+
"properties": {
269+
"repo": {
270+
"type": "string",
271+
"description": "Full repo name (owner/name) — currently used only in logging/provenance."
272+
},
273+
"pr_number": {
274+
"type": "integer",
275+
"description": "PR number the question was posted on."
276+
},
277+
"question": {
278+
"type": "string",
279+
"description": "The user's question, with @echidnabot mentions stripped."
280+
},
281+
"context": {
282+
"type": "string",
283+
"description": "Compact summary of recent verification jobs on this PR."
284+
}
285+
},
286+
"required": ["question"]
287+
}
262288
}
263289
]
264290
}

cartridges/echidna-llm-mcp/mod.js

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,94 @@ export async function handleTool(toolName, args) {
129129
return restGet(`/api/session/${id}/tree`);
130130
}
131131

132+
// ── Consultant-mode Q&A (Phase 6 of echidnabot bot-mode wiring) ────────
133+
case "consultant_qa": {
134+
// Args: { repo: string, pr_number: number, question: string, context: string }
135+
//
136+
// Composes existing ECHIDNA endpoints into a single markdown-formatted
137+
// Q&A response. Used by echidnabot when a Consultant-mode repo gets
138+
// an `@echidnabot` mention on a PR comment.
139+
//
140+
// Strategy (composition over a new endpoint):
141+
// 1. /api/search — keyword search over the 66,674-proof corpus
142+
// for proofs related to the user's question. Surfaces precedent
143+
// that the reviewer might find useful.
144+
// 2. /api/tactics/suggest — aspect-tag model gives tactic hints
145+
// for a freeform goal-shaped query.
146+
// 3. Format both into a single markdown answer block.
147+
//
148+
// No new ECHIDNA endpoint is needed; if one ever lands at /api/consult,
149+
// route preferentially to it from here.
150+
const question = (a.question ?? "").trim();
151+
if (!question) {
152+
return {
153+
status: 200,
154+
data: {
155+
answer:
156+
"_Mention received — no question text. Ping me with `@echidnabot " +
157+
"<your question>` and I'll look up related proofs and suggestions._",
158+
provenance: "no-op (empty question)",
159+
},
160+
};
161+
}
162+
163+
const [searchResp, suggestResp] = await Promise.all([
164+
restGet(`/api/search?q=${encodeURIComponent(question)}`),
165+
restPost("/api/tactics/suggest", {
166+
goal: question,
167+
top_k: 3,
168+
active_tags: [],
169+
}),
170+
]);
171+
172+
const lines = [];
173+
lines.push("## 🦔 echidnabot · Consultant — composed answer");
174+
lines.push("");
175+
lines.push(`> ${question.slice(0, 200)}`);
176+
lines.push("");
177+
178+
if (searchResp.status === 200 && Array.isArray(searchResp.data?.results) &&
179+
searchResp.data.results.length > 0) {
180+
lines.push("### 📚 Related precedent (corpus search)");
181+
lines.push("");
182+
for (const hit of searchResp.data.results.slice(0, 5)) {
183+
const name = hit.theorem ?? hit.name ?? hit.id ?? "?";
184+
const prover = hit.prover ?? "?";
185+
lines.push(`- \`${name}\` · ${prover}`);
186+
}
187+
lines.push("");
188+
}
189+
190+
if (suggestResp.status === 200 && Array.isArray(suggestResp.data?.suggestions) &&
191+
suggestResp.data.suggestions.length > 0) {
192+
lines.push("### 💡 Tactic hints (aspect-tag model)");
193+
lines.push("");
194+
for (const s of suggestResp.data.suggestions.slice(0, 3)) {
195+
const tactic = s.tactic ?? s.text ?? "?";
196+
const conf = s.confidence != null ? ` (${(s.confidence * 100).toFixed(0)}%)` : "";
197+
lines.push(`- \`${tactic}\`${conf}`);
198+
}
199+
lines.push("");
200+
}
201+
202+
if (lines.length === 4) {
203+
// Header + question, no search/suggest content — surface that.
204+
lines.push("_No related precedent or tactic suggestions found in the corpus._");
205+
lines.push(
206+
"_This response is a composition of /api/search + /api/tactics/suggest; " +
207+
"richer free-form Q&A awaits a dedicated /api/consult endpoint upstream._",
208+
);
209+
}
210+
211+
return {
212+
status: 200,
213+
data: {
214+
answer: lines.join("\n"),
215+
provenance: "echidna-llm-mcp/consultant_qa: search+tactics/suggest composite",
216+
},
217+
};
218+
}
219+
132220
default:
133221
return { status: 404, data: { error: `Unknown tool: ${toolName}` } };
134222
}

0 commit comments

Comments
 (0)