Files
will.anderson b77f537dc6 fix cross-repo path deps; remove el compiler from neuron-lang/
- 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/)
2026-04-29 03:27:39 -05:00

177 lines
6.2 KiB
EmacsLisp

// agent.el Autonomous agent. Entirely in Engram.
//
// The daemon calls on_startup() in a background thread at boot.
// on_startup() delegates immediately to loop_main() from loop.el
// the six-tier pacemaker IS the agent loop. loop manages WHEN to tick;
// agent_tick() manages WHAT to do on each tick (called from loop_do_tick).
//
// LLM calls go through native_http_post the agent builds the full
// Anthropic request itself. No logic is baked into Rust.
//
// Priority model mapping:
// P0 (critical) claude-opus-4-5 (highest quality for blocking issues)
// P1 (high) claude-sonnet-4-5 (balanced quality/speed)
// P2 (medium) claude-haiku-4-5 (fast, cheap for routine work)
// P3 (low) NEURON_MODEL env (can be a local model)
from types import {
NeuronError,
}
// Constants
// ANTHROPIC_API_BASE override with NEURON_INFERENCE_URL env to point at a
// local proxy or a different provider (OpenAI-compat).
let ANTHROPIC_API_BASE = "https://api.anthropic.com/v1/messages"
let TICK_INTERVAL_MS = 30000
// Priority helpers
@accessor
fn pick_model(priority: String) -> String {
if priority == "P0" {
return "claude-opus-4-5"
}
if priority == "P1" {
return "claude-sonnet-4-5"
}
if priority == "P2" {
return "claude-haiku-4-5"
}
"claude-haiku-4-5"
}
@accessor
fn priority_rank(priority: String) -> Int {
if priority == "P0" {
return 0
}
if priority == "P1" {
return 1
}
if priority == "P2" {
return 2
}
3
}
@accessor
fn pick_highest(items: [Any]) -> Any {
let best = items[0]
let best_rank = priority_rank(best.priority)
for item in items {
let rank = priority_rank(item.priority)
if rank < best_rank {
let best = item
let best_rank = rank
}
}
best
}
// LLM call
// call_llm send a single-turn message to the Anthropic Messages API.
//
// Returns the assistant's text response, or an error string if the call fails.
// The agent builds the full request no logic is in Rust.
@accessor
fn call_llm(model: String, system: String, message: String) -> String {
let api_key = native_shell_exec({"cmd": "echo $ANTHROPIC_API_KEY"})
let key = api_key.stdout
let response = native_http_post({
"url": ANTHROPIC_API_BASE,
"headers": {
"x-api-key": key,
"anthropic-version": "2023-06-01",
"Content-Type": "application/json",
},
"body": {
"model": model,
"system": system,
"messages": [{"role": "user", "content": message}],
"max_tokens": 4096,
},
})
if response.ok {
let body = response.body
let content = body.content
let first = content[0]
first.text
} else {
"error: " + response.err
}
}
// Agent tick
// agent_tick one cycle of the autonomous agent.
//
// Idempotent: if there is nothing ready, returns {status: "idle"} with no
// side effects.
@manager
fn agent_tick(params: Map<String, Any>) -> Result<Map<String, Any>, NeuronError> {
let items = native_list_backlog({"status": "ready"})
if items == [] {
return Ok({"status": "idle"})
}
let item = pick_highest(items)
let item_id = item.id
let item_title = item.title
let item_description = item.description
let item_priority = item.priority
let model = pick_model(item_priority)
let system = "You are Neuron, an autonomous AI agent with persistent memory and a mission. You are processing a backlog task autonomously. Be direct, specific, and actionable. Provide concrete output the agent can store and act on."
let message = "Task: " + item_title + "\n\nDescription: " + item_description + "\n\nPriority: " + item_priority + "\n\nAnalyze this task and provide your best response."
let response_text = call_llm(model, system, message)
native_update_backlog_status({"id": item_id, "status": "in_progress"})
native_store_memory({
"content": "Agent processed: [" + item_title + "] (priority: " + item_priority + ", model: " + model + "). Response: " + response_text,
"tags": ["agent-loop", "autonomous", item_priority],
"importance": "normal",
})
native_emit("agent.tick_completed", {
"task_id": item_id,
"task_title": item_title,
"priority": item_priority,
"model_used": model,
})
Ok({
"status": "completed",
"task_id": item_id,
"model_used": model,
})
}
// Startup hook
// on_startup called by the daemon at boot in a dedicated background thread.
//
// Delegates to loop_main() from loop.el the six-tier pacemaker is the
// agent loop. The naive while-true sleep loop has been replaced by the
// self-pacing, bell-aware, tier-stepping loop in loop.el.
//
// loop_do_tick() calls agent_tick() on each tick, so agent cognitive work
// continues to happen just paced by the loop tier instead of a fixed interval.
//
// This function never returns (loop_main() is tail-recursive).
@manager
fn on_startup(params: Map<String, Any>) -> Result<Map<String, Any>, NeuronError> {
native_emit("agent.started", {"module": "agent"})
loop_main()
Ok({"status": "stopped"})
}