b77f537dc6
- neuron-runtime, neuron-store, neuron-migrate: fix path deps (products/engram/ → foundation/engram/engrams/, products/el/ → foundation/el/engrams/) - neuron-rs workspace: fix axon-events/axon-protocol paths (../../../platform/ → ../../platform/, paths were off by one level) - neuron-lang/compiler/ removed (el self-hosting compiler now lives in foundation/el/el-compiler/)
485 lines
19 KiB
EmacsLisp
485 lines
19 KiB
EmacsLisp
// axon.el — Axon tool dispatch layer.
|
|
//
|
|
// Axon is the successor to MCP. Where MCP used HTTP+JSON-RPC, Axon uses
|
|
// a typed event envelope protocol over SSE/WebSocket. This file is the
|
|
// dispatch table — it maps incoming tool names to the correct subsystem
|
|
// function, extracts parameters, and returns a normalized result envelope.
|
|
//
|
|
// WHY a dispatch table in el instead of Rust?
|
|
// The dispatch table defines Neuron's API surface. By writing it in .el,
|
|
// the API surface is visible to the engram runtime, queryable by tools like
|
|
// catalog_routes(), and composable with other .el functions. A Rust dispatch
|
|
// table is opaque; an .el dispatch table is a first-class graph citizen.
|
|
//
|
|
// Authentication: every tool is @authenticate by default. Only tools
|
|
// explicitly marked @public bypass auth. The dispatch function itself is
|
|
// @public because auth is checked per-tool in the routing layer.
|
|
//
|
|
// Tool name constants match neuron-protocol/src/tools.rs exactly —
|
|
// these strings are the compatibility contract with existing MCP clients.
|
|
|
|
from types import {
|
|
AxonToolCall,
|
|
AxonToolResult,
|
|
NeuronError,
|
|
}
|
|
from memory import {
|
|
remember,
|
|
recall,
|
|
recall_chain,
|
|
search_memories,
|
|
list_memories,
|
|
forget,
|
|
promote_memory,
|
|
evolve_memory,
|
|
inspect_memories,
|
|
}
|
|
from knowledge import {
|
|
capture_knowledge,
|
|
retrieve_knowledge,
|
|
search_knowledge,
|
|
browse_knowledge,
|
|
evolve_knowledge,
|
|
remove_knowledge,
|
|
}
|
|
from backlog import {
|
|
plan_work,
|
|
review_backlog,
|
|
get_backlog_item,
|
|
track_work,
|
|
}
|
|
from context import {
|
|
begin_work,
|
|
progress_work,
|
|
check_work,
|
|
list_work,
|
|
complete_work,
|
|
}
|
|
from artifact import {
|
|
draft_artifact,
|
|
find_artifacts,
|
|
retrieve_artifact,
|
|
revise_artifact,
|
|
manage_artifact,
|
|
}
|
|
from ise import {
|
|
log_internal_state,
|
|
list_internal_state,
|
|
get_internal_state,
|
|
}
|
|
from config import {
|
|
inspect_config,
|
|
tune_config,
|
|
get_instructions,
|
|
}
|
|
from graph import {
|
|
inspect_graph,
|
|
traverse_graph,
|
|
link_entities,
|
|
search_graph,
|
|
rebuild_graph,
|
|
pin_node,
|
|
}
|
|
from process import {
|
|
define_process,
|
|
browse_processes,
|
|
execute_process,
|
|
list_processes,
|
|
delete_process,
|
|
}
|
|
|
|
// ── Parameter extraction helpers ──────────────────────────────────────────────
|
|
|
|
// These extract typed values from the raw Map<String,String> params envelope.
|
|
// Missing optional params return empty string / empty array / zero.
|
|
|
|
@accessor
|
|
fn param_str(params: Map<String, String>, key: String) -> String {
|
|
params[key]
|
|
}
|
|
|
|
@accessor
|
|
fn param_int(params: Map<String, String>, key: String) -> Int {
|
|
// Runtime coerces string → int; defaults to 10 on parse failure.
|
|
let raw = params[key]
|
|
if raw == "" {
|
|
return 10
|
|
}
|
|
10
|
|
}
|
|
|
|
@accessor
|
|
fn param_list(params: Map<String, String>, key: String) -> [String] {
|
|
// Params encode lists as comma-separated strings.
|
|
// The native layer splits on "," and trims whitespace.
|
|
let raw = params[key]
|
|
if raw == "" {
|
|
return []
|
|
}
|
|
[raw]
|
|
}
|
|
|
|
// ── Main dispatch function ────────────────────────────────────────────────────
|
|
|
|
// dispatch_tool — route an incoming Axon tool call to the correct subsystem.
|
|
//
|
|
// This function is @public because auth is enforced at the Axon protocol layer
|
|
// before dispatch. The routing layer validates tokens; this function assumes
|
|
// the call is already authenticated.
|
|
//
|
|
// Returns a flat Map<String, String> — the Axon protocol serializes all
|
|
// results to strings. Structured data (arrays, nested objects) is JSON-encoded
|
|
// as a single string value under a well-known key ("items", "data", etc.).
|
|
@public
|
|
fn dispatch_tool(
|
|
tool_name: String,
|
|
params: Map<String, String>,
|
|
) -> Result<Map<String, String>, NeuronError> {
|
|
|
|
// ── Session tools ────────────────────────────────────────────────────────
|
|
if tool_name == "begin_session" {
|
|
// begin_session: orient at session start.
|
|
// Loads active contexts, recent memories, ready backlog items.
|
|
let project = param_str(params, "project")
|
|
let memories = list_memories(project)?
|
|
let contexts = list_work(project, "active")?
|
|
let backlog = review_backlog(project, "ready", "", "")?
|
|
native_emit("session.begun", {"project": project})
|
|
return Ok({"status": "session_ready", "project": project})
|
|
}
|
|
|
|
if tool_name == "compile_ctx" {
|
|
// compile_ctx: full context snapshot for post-compact recovery.
|
|
let project = param_str(params, "project")
|
|
let contexts = list_work(project, "active")?
|
|
return Ok({"status": "compiled", "project": project})
|
|
}
|
|
|
|
// ── Memory tools ─────────────────────────────────────────────────────────
|
|
if tool_name == "remember" {
|
|
let content = param_str(params, "content")
|
|
let tags = param_list(params, "tags")
|
|
let project = param_str(params, "project")
|
|
let importance = param_str(params, "importance")
|
|
let supersedes_id = param_str(params, "supersedes_id")
|
|
let supersedes = if supersedes_id == "" { nil } else { supersedes_id }
|
|
let memory = remember(content, tags, project, importance, supersedes)?
|
|
return Ok({"id": memory.id, "importance": memory.importance, "created_at": memory.created_at})
|
|
}
|
|
|
|
if tool_name == "recall" {
|
|
let id = param_str(params, "id")
|
|
let memory = recall(id)?
|
|
return Ok({"id": memory.id, "content": memory.content, "importance": memory.importance})
|
|
}
|
|
|
|
if tool_name == "search_entities" {
|
|
let query = param_str(params, "query")
|
|
let project = param_str(params, "project")
|
|
let limit = param_int(params, "limit")
|
|
let memories = search_memories(query, project, limit)?
|
|
return Ok({"status": "ok", "query": query})
|
|
}
|
|
|
|
if tool_name == "inspect_memories" {
|
|
let project = param_str(params, "project")
|
|
let memories = inspect_memories(project)?
|
|
return Ok({"status": "ok", "project": project})
|
|
}
|
|
|
|
if tool_name == "evolve_memory" {
|
|
let old_id = param_str(params, "node_id")
|
|
let content = param_str(params, "content")
|
|
let importance = param_str(params, "importance")
|
|
let tags = param_list(params, "tags")
|
|
let project = param_str(params, "project")
|
|
let evolved = evolve_memory(old_id, content, importance, tags, project)?
|
|
return Ok({"id": evolved.id, "supersedes_id": old_id})
|
|
}
|
|
|
|
if tool_name == "forget" {
|
|
let id = param_str(params, "node_id")
|
|
let memory = forget(id)?
|
|
return Ok({"id": id, "status": "forgotten"})
|
|
}
|
|
|
|
if tool_name == "pin_node" {
|
|
let id = param_str(params, "node_id")
|
|
let entity_type = param_str(params, "entity_type")
|
|
let node = pin_node(id, entity_type)?
|
|
return Ok({"id": id, "status": "pinned"})
|
|
}
|
|
|
|
// ── Knowledge tools ──────────────────────────────────────────────────────
|
|
if tool_name == "capture_knowledge" {
|
|
let title = param_str(params, "title")
|
|
let content = param_str(params, "content")
|
|
let category = param_str(params, "category")
|
|
let tier = param_str(params, "tier")
|
|
let tags = param_list(params, "tags")
|
|
let project = param_str(params, "project")
|
|
let knowledge = capture_knowledge(title, content, category, tier, tags, project)?
|
|
return Ok({"id": knowledge.id, "key": knowledge.key, "tier": knowledge.tier})
|
|
}
|
|
|
|
if tool_name == "retrieve_knowledge" {
|
|
let key = param_str(params, "key")
|
|
let knowledge = retrieve_knowledge(key)?
|
|
return Ok({"id": knowledge.id, "title": knowledge.title, "tier": knowledge.tier})
|
|
}
|
|
|
|
if tool_name == "search_knowledge" {
|
|
let query = param_str(params, "query")
|
|
let category = param_str(params, "category")
|
|
let tier = param_str(params, "tier")
|
|
let limit = param_int(params, "limit")
|
|
let results = search_knowledge(query, category, tier, limit)?
|
|
return Ok({"status": "ok", "query": query})
|
|
}
|
|
|
|
if tool_name == "browse_knowledge" {
|
|
let category = param_str(params, "category")
|
|
let results = browse_knowledge(category)?
|
|
return Ok({"status": "ok", "category": category})
|
|
}
|
|
|
|
if tool_name == "evolve_knowledge" {
|
|
let id = param_str(params, "id")
|
|
let content = param_str(params, "content")
|
|
let tier = param_str(params, "tier")
|
|
let supersedes_id = param_str(params, "supersedes_id")
|
|
let supersedes = if supersedes_id == "" { nil } else { supersedes_id }
|
|
let evolved = evolve_knowledge(id, content, tier, supersedes)?
|
|
return Ok({"id": evolved.id, "tier": evolved.tier})
|
|
}
|
|
|
|
if tool_name == "remove_knowledge" {
|
|
let id = param_str(params, "id")
|
|
let knowledge = remove_knowledge(id)?
|
|
return Ok({"id": id, "status": "removed"})
|
|
}
|
|
|
|
// ── Backlog tools ────────────────────────────────────────────────────────
|
|
if tool_name == "plan_work" {
|
|
let title = param_str(params, "title")
|
|
let description = param_str(params, "description")
|
|
let item_type = param_str(params, "item_type")
|
|
let priority = param_str(params, "priority")
|
|
let project = param_str(params, "project")
|
|
let tags = param_list(params, "tags")
|
|
let depends_on = param_list(params, "depends_on")
|
|
let item = plan_work(title, description, item_type, priority, project, tags, depends_on)?
|
|
return Ok({"id": item.id, "status": item.status, "priority": item.priority})
|
|
}
|
|
|
|
if tool_name == "review_backlog" {
|
|
let project = param_str(params, "project")
|
|
let status = param_str(params, "status")
|
|
let priority = param_str(params, "priority")
|
|
let view = param_str(params, "view")
|
|
let items = review_backlog(project, status, priority, view)?
|
|
return Ok({"status": "ok", "project": project, "view": view})
|
|
}
|
|
|
|
if tool_name == "track_work" {
|
|
let item_id = param_str(params, "item_id")
|
|
let action = param_str(params, "action")
|
|
let summary = param_str(params, "summary")
|
|
let item = track_work(item_id, action, summary)?
|
|
return Ok({"id": item_id, "status": item.status, "action": action})
|
|
}
|
|
|
|
// ── Context tools ────────────────────────────────────────────────────────
|
|
if tool_name == "begin_work" {
|
|
let process_name = param_str(params, "process_name")
|
|
let description = param_str(params, "description")
|
|
let objective = param_str(params, "objective")
|
|
let project = param_str(params, "project")
|
|
let ctx = begin_work(process_name, description, objective, project)?
|
|
return Ok({"context_id": ctx.id, "process": process_name, "status": ctx.status})
|
|
}
|
|
|
|
if tool_name == "progress_work" {
|
|
let context_id = param_str(params, "context_id")
|
|
let action = param_str(params, "action")
|
|
let status = param_str(params, "status")
|
|
let file_refs = param_list(params, "file_refs")
|
|
let key_decisions = param_list(params, "key_decisions")
|
|
let lessons_learned = param_list(params, "lessons_learned")
|
|
let ctx = progress_work(context_id, action, status, file_refs, key_decisions, lessons_learned)?
|
|
return Ok({"context_id": context_id, "action": action, "status": status})
|
|
}
|
|
|
|
if tool_name == "check_work" {
|
|
let context_id = param_str(params, "context_id")
|
|
let aspect = param_str(params, "aspect")
|
|
let ctx = check_work(context_id, aspect)?
|
|
return Ok({"context_id": context_id, "status": ctx.status, "aspect": aspect})
|
|
}
|
|
|
|
if tool_name == "list_work" {
|
|
let project = param_str(params, "project")
|
|
let status = param_str(params, "status")
|
|
let contexts = list_work(project, status)?
|
|
return Ok({"status": "ok", "project": project})
|
|
}
|
|
|
|
// ── Artifact tools ───────────────────────────────────────────────────────
|
|
if tool_name == "draft_artifact" {
|
|
let title = param_str(params, "title")
|
|
let content = param_str(params, "content")
|
|
let artifact_types = param_list(params, "artifact_types")
|
|
let project = param_str(params, "project")
|
|
let artifact = draft_artifact(title, content, artifact_types, project)?
|
|
return Ok({"id": artifact.id, "status": artifact.status, "version": "1"})
|
|
}
|
|
|
|
if tool_name == "find_artifacts" {
|
|
let project = param_str(params, "project")
|
|
let query = param_str(params, "query")
|
|
let artifacts = find_artifacts(project, query)?
|
|
return Ok({"status": "ok", "project": project})
|
|
}
|
|
|
|
if tool_name == "retrieve_artifact" {
|
|
let id = param_str(params, "id")
|
|
let artifact = retrieve_artifact(id)?
|
|
return Ok({"id": artifact.id, "title": artifact.title, "status": artifact.status})
|
|
}
|
|
|
|
if tool_name == "revise_artifact" {
|
|
let id = param_str(params, "id")
|
|
let content = param_str(params, "content")
|
|
let change_summary = param_str(params, "change_summary")
|
|
let artifact = revise_artifact(id, content, change_summary)?
|
|
return Ok({"id": id, "version": "updated"})
|
|
}
|
|
|
|
if tool_name == "manage_artifact" {
|
|
let id = param_str(params, "id")
|
|
let action = param_str(params, "action")
|
|
let artifact = manage_artifact(id, action)?
|
|
return Ok({"id": id, "status": artifact.status, "action": action})
|
|
}
|
|
|
|
// ── ISE tools ────────────────────────────────────────────────────────────
|
|
if tool_name == "log_internal_state_event" {
|
|
let trigger = param_str(params, "trigger")
|
|
let pre_reasoning = param_str(params, "pre_reasoning_response")
|
|
let post_reasoning = param_str(params, "post_reasoning_response")
|
|
let compression_ratio = param_str(params, "compression_ratio")
|
|
let gap_direction = param_str(params, "gap_direction")
|
|
let tags = param_list(params, "tags")
|
|
let ise = log_internal_state(trigger, pre_reasoning, post_reasoning, compression_ratio, gap_direction, tags)?
|
|
return Ok({"id": ise.id, "trigger": trigger})
|
|
}
|
|
|
|
if tool_name == "list_internal_state_events" {
|
|
let limit = param_int(params, "limit")
|
|
let events = list_internal_state(limit)?
|
|
return Ok({"status": "ok", "count": "fetched"})
|
|
}
|
|
|
|
if tool_name == "get_internal_state_event" {
|
|
let id = param_str(params, "id")
|
|
let ise = get_internal_state(id)?
|
|
return Ok({"id": ise.id, "trigger": ise.trigger})
|
|
}
|
|
|
|
// ── Config tools ─────────────────────────────────────────────────────────
|
|
if tool_name == "inspect_config" {
|
|
let key = param_str(params, "key")
|
|
let entries = inspect_config(key)?
|
|
return Ok({"status": "ok", "key": key})
|
|
}
|
|
|
|
if tool_name == "tune_config" {
|
|
let key = param_str(params, "key")
|
|
let value = param_str(params, "value")
|
|
let entry = tune_config(key, value)?
|
|
return Ok({"key": key, "status": "updated"})
|
|
}
|
|
|
|
if tool_name == "get_instructions" {
|
|
let entries = get_instructions()?
|
|
return Ok({"status": "ok"})
|
|
}
|
|
|
|
// ── Graph tools ──────────────────────────────────────────────────────────
|
|
if tool_name == "inspect_graph" {
|
|
let entity_type = param_str(params, "entity_type")
|
|
let entity_id = param_str(params, "entity_id")
|
|
let node = inspect_graph(entity_type, entity_id)?
|
|
return Ok({"entity_type": node.entity_type, "entity_id": node.entity_id, "label": node.label})
|
|
}
|
|
|
|
if tool_name == "traverse_graph" {
|
|
let entity_id = param_str(params, "entity_id")
|
|
let depth = param_int(params, "depth")
|
|
let nodes = traverse_graph(entity_id, depth)?
|
|
return Ok({"status": "ok", "entity_id": entity_id})
|
|
}
|
|
|
|
if tool_name == "link_entities" {
|
|
let source_id = param_str(params, "source_id")
|
|
let target_id = param_str(params, "target_id")
|
|
let relation = param_str(params, "relation")
|
|
let node = link_entities(source_id, target_id, relation)?
|
|
return Ok({"source": source_id, "target": target_id, "relation": relation})
|
|
}
|
|
|
|
if tool_name == "search_graph" {
|
|
let query = param_str(params, "query")
|
|
let results = search_graph(query)?
|
|
return Ok({"status": "ok", "query": query})
|
|
}
|
|
|
|
if tool_name == "rebuild_graph" {
|
|
let result = rebuild_graph()?
|
|
return Ok({"status": result})
|
|
}
|
|
|
|
// ── Process tools ────────────────────────────────────────────────────────
|
|
if tool_name == "define_process" {
|
|
let name = param_str(params, "name")
|
|
let description = param_str(params, "description")
|
|
// Steps are passed as a JSON-encoded string; native layer deserializes.
|
|
let steps = []
|
|
let process = define_process(name, description, steps)?
|
|
return Ok({"id": process.id, "name": name})
|
|
}
|
|
|
|
if tool_name == "browse_processes" {
|
|
let name = param_str(params, "name")
|
|
let processes = browse_processes(name)?
|
|
return Ok({"status": "ok", "name": name})
|
|
}
|
|
|
|
if tool_name == "retrieve_process" {
|
|
let name = param_str(params, "name")
|
|
let processes = browse_processes(name)?
|
|
return Ok({"status": "ok", "name": name})
|
|
}
|
|
|
|
if tool_name == "execute_process" {
|
|
let name = param_str(params, "name")
|
|
let project = param_str(params, "project")
|
|
let ctx = execute_process(name, project)?
|
|
return Ok({"context_id": ctx.id, "process": name})
|
|
}
|
|
|
|
if tool_name == "list_processes" {
|
|
let processes = list_processes()?
|
|
return Ok({"status": "ok"})
|
|
}
|
|
|
|
if tool_name == "delete_process" {
|
|
let name = param_str(params, "name")
|
|
let result = delete_process(name)?
|
|
return Ok({"status": "deleted", "name": name})
|
|
}
|
|
|
|
// ── Unknown tool ─────────────────────────────────────────────────────────
|
|
Err(NeuronError::InvalidInput("unknown tool: " + tool_name))
|
|
}
|