Skip to content

Commit 1e2aa21

Browse files
hyperpolymathclaude
andcommitted
feat: implement ECHIDNA Phase 2 — interactive sessions & tactic suggestions
Connect PanLL to ECHIDNA's real session-based REST API (port 8000, /api/v1/) for interactive proof sessions with ML-powered tactic suggestions. Fix the default URL from the GraphQL port (:8080) to the REST port (:8000). - Model: add echidnaProofStatus (6 states), extend echidnaSessionState with prover/goal/status/proofScript/timeElapsed/errorMessage, add args to echidnaTacticSuggestion, add tacticInput/sessionLoading to echidnaState - Rust: 4 new Tauri commands (create_session, get_session, apply_tactic, suggest_tactics) with 2 unit tests for URL construction and serialisation - TauriCmd: 4 TEA command wrappers for session lifecycle - Update: 3 JSON parsers (parseEchidnaSession, parseTacticResponse, parseTacticSuggestions), replace all 8 Phase 1 stubs with real handlers, auto-request suggestions after session creation and tactic application - PaneN: 6 new UI components — session controls, status badge (colour-coded), goal list (active goal highlighted), manual tactic input, suggestion ribbon (clickable chips sorted by confidence), proof script history - Tests: 13 new tests covering all session/tactic message handlers (24 total) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 4c49d30 commit 1e2aa21

7 files changed

Lines changed: 1256 additions & 23 deletions

File tree

src-tauri/src/main.rs

Lines changed: 158 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1314,7 +1314,7 @@ fn verisimdb_get_entity(entity_id: String) -> Result<String, String> {
13141314
// ECHIDNA Theorem Prover Backend
13151315
// ===========================================================================
13161316

1317-
const DEFAULT_ECHIDNA_URL: &str = "http://localhost:8080/api";
1317+
const DEFAULT_ECHIDNA_URL: &str = "http://localhost:8000/api/v1";
13181318

13191319
/// Resolve the ECHIDNA API base URL from environment or default.
13201320
/// Override with ECHIDNA_URL env var for non-default deployments.
@@ -1460,6 +1460,118 @@ fn echidna_search_theorems(query: String) -> Result<String, String> {
14601460
}
14611461
}
14621462

1463+
/// POST /proofs — create a new interactive proof session.
1464+
/// Sends `{"goal": goal, "prover": prover}` and returns the ProofResponse JSON
1465+
/// with session ID, initial goals, and status (201 Created).
1466+
#[tauri::command]
1467+
fn echidna_create_session(goal: String, prover: String) -> Result<String, String> {
1468+
let url = format!("{}/proofs", echidna_url());
1469+
let client = reqwest::blocking::Client::builder()
1470+
.timeout(std::time::Duration::from_secs(30))
1471+
.build()
1472+
.map_err(|e| format!("HTTP client error: {}", e))?;
1473+
1474+
let payload = json!({ "goal": goal, "prover": prover });
1475+
1476+
match client.post(&url).json(&payload).send() {
1477+
Ok(resp) => {
1478+
let status = resp.status();
1479+
let body = resp.text().unwrap_or_default();
1480+
if status.is_success() {
1481+
Ok(body)
1482+
} else {
1483+
Err(format!("Session creation failed ({}): {}", status, body))
1484+
}
1485+
}
1486+
Err(e) => Err(format!("Session creation request failed: {}", e)),
1487+
}
1488+
}
1489+
1490+
/// GET /proofs/{id} — retrieve the current state of a proof session.
1491+
/// Returns the ProofResponse JSON with goals, status, and applied tactics.
1492+
#[tauri::command]
1493+
fn echidna_get_session(session_id: String) -> Result<String, String> {
1494+
let url = format!("{}/proofs/{}", echidna_url(), session_id);
1495+
let client = reqwest::blocking::Client::builder()
1496+
.timeout(std::time::Duration::from_secs(10))
1497+
.build()
1498+
.map_err(|e| format!("HTTP client error: {}", e))?;
1499+
1500+
match client.get(&url).send() {
1501+
Ok(resp) => {
1502+
let status = resp.status();
1503+
let body = resp.text().unwrap_or_default();
1504+
if status.is_success() {
1505+
Ok(body)
1506+
} else {
1507+
Err(format!("Get session failed ({}): {}", status, body))
1508+
}
1509+
}
1510+
Err(e) => Err(format!("Get session request failed: {}", e)),
1511+
}
1512+
}
1513+
1514+
/// POST /proofs/{id}/tactics — apply a tactic to an active proof session.
1515+
/// Sends `{"name": name, "args": args}` and returns the TacticResponse JSON
1516+
/// with success flag and updated proof state.
1517+
#[tauri::command]
1518+
fn echidna_apply_tactic(
1519+
session_id: String,
1520+
name: String,
1521+
args: Vec<String>,
1522+
) -> Result<String, String> {
1523+
let url = format!("{}/proofs/{}/tactics", echidna_url(), session_id);
1524+
let client = reqwest::blocking::Client::builder()
1525+
.timeout(std::time::Duration::from_secs(30))
1526+
.build()
1527+
.map_err(|e| format!("HTTP client error: {}", e))?;
1528+
1529+
let payload = json!({ "name": name, "args": args });
1530+
1531+
match client.post(&url).json(&payload).send() {
1532+
Ok(resp) => {
1533+
let status = resp.status();
1534+
let body = resp.text().unwrap_or_default();
1535+
if status.is_success() {
1536+
Ok(body)
1537+
} else {
1538+
Err(format!("Apply tactic failed ({}): {}", status, body))
1539+
}
1540+
}
1541+
Err(e) => Err(format!("Apply tactic request failed: {}", e)),
1542+
}
1543+
}
1544+
1545+
/// GET /proofs/{id}/tactics/suggest?limit=N — request ML-powered tactic suggestions.
1546+
/// The ML advisor (Julia :8090) generates suggestions with confidence scores,
1547+
/// falling back to prover heuristics if the advisor is unavailable.
1548+
#[tauri::command]
1549+
fn echidna_suggest_tactics(session_id: String, limit: usize) -> Result<String, String> {
1550+
let url = format!(
1551+
"{}/proofs/{}/tactics/suggest?limit={}",
1552+
echidna_url(),
1553+
session_id,
1554+
limit
1555+
);
1556+
let client = reqwest::blocking::Client::builder()
1557+
.timeout(std::time::Duration::from_secs(15))
1558+
.build()
1559+
.map_err(|e| format!("HTTP client error: {}", e))?;
1560+
1561+
match client.get(&url).send() {
1562+
Ok(resp) => {
1563+
let status = resp.status();
1564+
let body = resp.text().unwrap_or_default();
1565+
if status.is_success() {
1566+
Ok(body)
1567+
} else {
1568+
Err(format!("Tactic suggestions failed ({}): {}", status, body))
1569+
}
1570+
}
1571+
Err(e) => Err(format!("Tactic suggestions request failed: {}", e)),
1572+
}
1573+
}
1574+
14631575
#[cfg(test)]
14641576
mod echidna_tests {
14651577
use super::*;
@@ -1479,6 +1591,47 @@ mod echidna_tests {
14791591
// Clean up so other tests aren't affected.
14801592
std::env::remove_var("ECHIDNA_URL");
14811593
}
1594+
1595+
#[test]
1596+
fn echidna_session_url_construction() {
1597+
std::env::remove_var("ECHIDNA_URL");
1598+
let base = echidna_url();
1599+
let session_url = format!("{}/proofs/{}", base, "test-session-123");
1600+
assert_eq!(
1601+
session_url,
1602+
"http://localhost:8000/api/v1/proofs/test-session-123"
1603+
);
1604+
1605+
let tactic_url = format!("{}/proofs/{}/tactics", base, "sess-abc");
1606+
assert_eq!(
1607+
tactic_url,
1608+
"http://localhost:8000/api/v1/proofs/sess-abc/tactics"
1609+
);
1610+
1611+
let suggest_url = format!(
1612+
"{}/proofs/{}/tactics/suggest?limit={}",
1613+
base, "sess-abc", 5
1614+
);
1615+
assert_eq!(
1616+
suggest_url,
1617+
"http://localhost:8000/api/v1/proofs/sess-abc/tactics/suggest?limit=5"
1618+
);
1619+
}
1620+
1621+
#[test]
1622+
fn echidna_prover_serialisation() {
1623+
let payload = json!({ "goal": "forall x, x + 0 = x", "prover": "lean4" });
1624+
let obj = payload.as_object().unwrap();
1625+
assert_eq!(obj.get("goal").unwrap().as_str().unwrap(), "forall x, x + 0 = x");
1626+
assert_eq!(obj.get("prover").unwrap().as_str().unwrap(), "lean4");
1627+
1628+
let tactic_payload = json!({ "name": "intro", "args": ["x", "y"] });
1629+
let tobj = tactic_payload.as_object().unwrap();
1630+
assert_eq!(tobj.get("name").unwrap().as_str().unwrap(), "intro");
1631+
let args = tobj.get("args").unwrap().as_array().unwrap();
1632+
assert_eq!(args.len(), 2);
1633+
assert_eq!(args[0].as_str().unwrap(), "x");
1634+
}
14821635
}
14831636

14841637
fn main() {
@@ -1508,6 +1661,10 @@ fn main() {
15081661
echidna_prove,
15091662
echidna_verify,
15101663
echidna_search_theorems,
1664+
echidna_create_session,
1665+
echidna_get_session,
1666+
echidna_apply_tactic,
1667+
echidna_suggest_tactics,
15111668
])
15121669
.setup(|app| {
15131670
#[cfg(debug_assertions)]

src/Model.res

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -318,25 +318,43 @@ type echidnaDispatchResult = {
318318
}
319319

320320
/// A tactic suggestion from the ECHIDNA ML advisor.
321-
/// Phase 2 — type defined now, handler returns model unchanged.
321+
/// Includes tactic name, arguments, confidence score, aspect tags, and description.
322+
/// The ML advisor (Julia :8090) or prover fallback populates these.
322323
type echidnaTacticSuggestion = {
323324
tactic: string,
325+
args: array<string>,
324326
confidence: float,
325327
aspectTags: array<string>,
326328
description: string,
327329
}
328330

329-
/// Interactive proof session state.
330-
/// Phase 2 — type defined now, handler returns model unchanged.
331+
/// ECHIDNA proof session status — maps to the ProofResponse status field
332+
/// from the ECHIDNA REST API (/api/v1/proofs).
333+
type echidnaProofStatus =
334+
| Pending // Session created, awaiting first tactic
335+
| InProgress // Tactics being applied, goals remaining
336+
| ProofSuccess // All goals discharged
337+
| ProofFailed // Proof attempt failed
338+
| ProofTimeout // Solver timed out
339+
| ProofError // Internal error during proof
340+
341+
/// Interactive proof session state — mirrors ECHIDNA's ProofResponse.
342+
/// Tracks session identity, prover, goals, applied tactics, and timing.
331343
type echidnaSessionState = {
332344
sessionId: string,
345+
prover: string,
346+
goal: string,
347+
status: echidnaProofStatus,
333348
goals: array<string>,
349+
proofScript: array<string>,
334350
complete: bool,
335351
tacticsApplied: array<string>,
352+
timeElapsed: option<float>,
353+
errorMessage: option<string>,
336354
}
337355

338356
/// ECHIDNA backend state — tracks connection, prover catalog, proof lifecycle,
339-
/// interactive session (Phase 2), and tactic suggestions (Phase 2).
357+
/// interactive session, and tactic suggestions.
340358
type echidnaState = {
341359
connected: bool,
342360
endpoint: string,
@@ -350,6 +368,8 @@ type echidnaState = {
350368
selectedProver: option<string>,
351369
proofInput: string,
352370
menuExpanded: bool,
371+
tacticInput: string,
372+
sessionLoading: bool,
353373
}
354374

355375
/// The complete Model
@@ -511,7 +531,7 @@ let init = (): model => {
511531
},
512532
echidna: {
513533
connected: false,
514-
endpoint: "http://localhost:8080/api",
534+
endpoint: "http://localhost:8000/api/v1",
515535
version: None,
516536
provers: [],
517537
lastProofResult: None,
@@ -522,6 +542,8 @@ let init = (): model => {
522542
selectedProver: None,
523543
proofInput: "",
524544
menuExpanded: false,
545+
tacticInput: "",
546+
sessionLoading: false,
525547
},
526548
humidity: Medium,
527549
viewMode: DarkStart,

src/Msg.res

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -146,14 +146,16 @@ type echidnaMsg =
146146
// Theorem search
147147
| SearchTheorems(string)
148148
| SearchResult(result<string, string>)
149-
// Interactive sessions (Phase 2 — stub)
149+
// Interactive sessions
150150
| CreateSession
151151
| SessionCreated(result<string, string>)
152-
| ApplyTactic(string)
152+
| ApplyTactic(string, array<string>)
153153
| TacticApplied(result<string, string>)
154154
| GetSessionState
155155
| SessionStateLoaded(result<string, string>)
156-
// Tactic suggestions (Phase 2 — stub)
156+
| CancelSession
157+
| UpdateTacticInput(string)
158+
// Tactic suggestions
157159
| RequestTacticSuggestions
158160
| TacticSuggestionsLoaded(result<string, string>)
159161
// UI state

0 commit comments

Comments
 (0)