Skip to content
This repository was archived by the owner on Sep 3, 2026. It is now read-only.

Repository files navigation

lummy_agent

Experimental local-first LLM agent runtime on Gleam/BEAM.

This repository is also a meta-experiment: can a coding agent build and evolve a practical agent runtime in mostly pure Gleam, with BEAM/JS externals kept at typed boundary modules only.

Current status: experimental, functional local-first runtime.

Implemented now:

  • Gleam project skeleton
  • root supervisor
  • HTTP server via Mist + Wisp
  • GET /health
  • GET /ready
  • actor-backed HTTP session API
  • SSE session/run event streams
  • persisted event envelopes in storage backend
  • SSE replay via Last-Event-ID
  • JSON event-log endpoints for debugging
  • scored notes-memory recall with auto-writeback + importance tags
  • session memory debug/read endpoint
  • dedicated run actor + run supervisor foundation
  • run lifecycle routed through run actor with progress events
  • run scratchpad + plan task state on runs
  • tool call lifecycle routed through run actor with persisted events
  • tool result application and assistant response loop through run actor
  • local async built-in tool runtime with timeout + retry handling
  • per-run provider fallback chain selection
  • per-run tool policy overrides (allowed tools, capabilities, max risk)
  • basic agent executor route for end-to-end local runs
  • provider abstraction with basic-local, OpenRouter, and OpenAI-compatible adapters
  • OpenRouter and OpenAI-compatible verify routes for live configuration checks
  • true upstream incremental OpenRouter stream transport
  • persisted run.chunk events for assistant output
  • persisted memory.writeback_failed observability events
  • env-based config loading
  • domain error taxonomy
  • typed IDs, message/session/run/tool-call models
  • pure session/run/tool-call state transitions
  • repository contracts for sessions/messages/runs/tool calls and event log
  • local durable persistence adapter with schema versioning
  • session actor transaction pipeline for atomic session/run/tool/event commits
  • session supervisor + session manager runtime wiring
  • in-memory event bus for live fan-out
  • tests for config, transport, domain core, persistence, session runtime, HTTP API, tools, and event bus
  • CI workflow

Prerequisites

  • Erlang/OTP 28+
  • Gleam 1.15+
  • rebar3 on PATH

Run

gleam run

Default bind: http://localhost:4000.

Agent-only runtime for debugging:

cd agent && gleam run

Health check:

curl http://localhost:4000/health

Optional: create .env in project root. Runtime loads it at startup via glenvy before config parsing. Real process env still wins over .env values.

Example:

LUMMY_OPENROUTER_API_KEY=your_key_here
LUMMY_DEFAULT_PROVIDER_ID=openrouter
LUMMY_DEFAULT_MODEL_SELECTION=openai/gpt-4o-mini

Quickstart

Copy-paste flow:

  • create session
  • append user message
  • start /api/v1/sessions/:id/agent-runs
  • watch SSE or inspect event-log/tool-calls/session snapshot

Full example doc:

  • docs/quickstart.md

Replay missed SSE events:

curl -N -H 'Last-Event-ID: 1' http://localhost:4000/api/v1/sessions/<session-id>/events

Read persisted event backlog:

curl http://localhost:4000/api/v1/sessions/<session-id>/event-log
curl http://localhost:4000/api/v1/runs/<run-id>/event-log?after=1

Inspect scored recalled memory for session:

curl http://localhost:4000/api/v1/sessions/<session-id>/memory
curl http://localhost:4000/api/v1/sessions/<session-id>/memory?query=gleam%20releases&limit=3

Drive run lifecycle through run actor:

curl -X POST http://localhost:4000/api/v1/runs/<run-id>/transition \
  -H 'content-type: application/json' \
  -d '{"state":"loading_context","occurred_at":"2026-04-16T06:00:11Z"}'

Drive tool call lifecycle through run actor:

curl -X POST http://localhost:4000/api/v1/runs/<run-id>/tool-calls \
  -H 'content-type: application/json' \
  -d '{"tool_call_id":"tool-call-1","tool_name":"calculator","input_json":"{\"expr\":\"1+1\"}","started_at":"2026-04-16T06:30:11Z"}'

Apply tool result and resume model loop:

curl -X POST http://localhost:4000/api/v1/tool-calls/<tool-call-id>/apply-result \
  -H 'content-type: application/json' \
  -d '{"ended_at":"2026-04-16T06:30:12Z","output_json":"{\"result\":2}","message_id":"message-tool-1","content":"{\"result\":2}","created_at":"2026-04-16T06:30:12Z","resume_at":"2026-04-16T06:30:13Z"}'

Complete run with assistant response:

curl -X POST http://localhost:4000/api/v1/runs/<run-id>/respond \
  -H 'content-type: application/json' \
  -d '{"message_id":"message-assistant-1","content":"The answer is 2","created_at":"2026-04-16T06:30:14Z","ended_at":"2026-04-16T06:30:15Z","input_tokens":10,"output_tokens":6,"total_tokens":16,"estimated_cost_micros":100}'

Run higher-level basic agent path:

curl -X POST http://localhost:4000/api/v1/sessions/<session-id>/agent-runs \
  -H 'content-type: application/json' \
  -d '{"run_id":"run-1","trigger_message_id":"message-1","model_selection":"openai/gpt-4o-mini","provider_id":"openrouter","started_at":"2026-04-16T06:30:10Z"}'

Run with provider fallback chain and tighter tool policy:

curl -X POST http://localhost:4000/api/v1/sessions/<session-id>/agent-runs \
  -H 'content-type: application/json' \
  -d '{"run_id":"run-2","trigger_message_id":"message-1","model_selection":"openai/gpt-4o-mini","provider_id":"openrouter","provider_fallback_ids":["basic-local"],"skill_ids":["research"],"started_at":"2026-04-16T06:31:10Z","allowed_tools":["calculator","notes_memory"],"allowed_tool_capabilities":["math","memory"],"max_tool_risk":"low"}'

Verify OpenRouter wiring directly:

curl -X POST http://localhost:4000/api/v1/providers/openrouter/verify \
  -H 'content-type: application/json' \
  -d '{"model":"openai/gpt-4o-mini","prompt":"Say hello in five words."}'

Verify OpenAI-compatible wiring directly:

curl -X POST http://localhost:4000/api/v1/providers/openai-compatible/verify \
  -H 'content-type: application/json' \
  -d '{"model":"gpt-4o-mini","prompt":"Say hello in five words."}'

Simple Lustre UI

UI source lives in ui/ and is served by backend at GET /.

Build development assets:

bash scripts/build-ui-dev.sh

This inlines CSS imports only, keeps JS unminified, and skips asset hashing/compression.

Build production assets:

bash scripts/build-ui-prod.sh

This runs the Lustre JS minifier, PostCSS normalization/autoprefixing/CSS minify, writes an asset version, and emits gzip/Brotli assets for production serving.

Then run server and open:

gleam run
# browse http://localhost:4000

Notes:

  • UI currently targets openrouter only
  • UI talks to same-origin backend routes, so no CORS setup needed
  • backend still requires LUMMY_OPENROUTER_API_KEY
  • UI opens live session SSE stream at /api/v1/sessions/:id/events
  • typing / opens slash commands for /new and discovered /skill:<name> entries
  • UI includes debug pane for HTTP actions, SSE status, and streamed events
  • backend now logs request + run/provider/tool lifecycle to Erlang logger

Config

Variable Default Notes
LUMMY_SERVICE_NAME lummy_agent Service label in responses
LUMMY_ENV dev Environment label
LUMMY_HOST localhost Mist bind target
LUMMY_PORT 4000 HTTP port
LUMMY_DATABASE_PATH lummy_agent.db Local SQLite persistence file
LUMMY_SECRET_KEY_BASE dev fallback Must be at least 64 bytes
LUMMY_DEFAULT_PROVIDER_ID basic-local Default provider id when request says default or leaves config-driven selection
LUMMY_DEFAULT_PROVIDER_FALLBACK_IDS `` Comma-separated fallback chain appended after per-run fallback ids
LUMMY_DEFAULT_MODEL_SELECTION basic-local Default model name when request says default
LUMMY_OPENROUTER_API_KEY `` Required for provider id openrouter
LUMMY_OPENROUTER_BASE_URL https://openrouter.ai/api/v1 OpenRouter API base URL
LUMMY_OPENROUTER_SITE_URL http://localhost Optional HTTP-Referer header
LUMMY_OPENROUTER_APP_NAME lummy_agent Optional X-Title header
LUMMY_OPENROUTER_TIMEOUT_MS 30000 HTTP timeout for OpenRouter requests
LUMMY_OPENAI_COMPATIBLE_API_KEY `` API key for OpenAI-compatible endpoint
LUMMY_OPENAI_COMPATIBLE_BASE_URL https://api.openai.com/v1 Base URL for OpenAI-compatible provider
LUMMY_OPENAI_COMPATIBLE_TIMEOUT_MS 30000 HTTP timeout for OpenAI-compatible requests
LUMMY_AGENT_SYSTEM_PROMPT built-in local-first prompt Base system prompt for model requests
LUMMY_AGENT_CONTEXT_MESSAGE_LIMIT 12 Recent message count kept verbatim in prompt window
LUMMY_AGENT_CONTEXT_SUMMARY_CHARS 600 Max summary chars injected for older context
LUMMY_AGENT_CONTEXT_SUMMARY_MODE model model or deterministic
LUMMY_AGENT_CONTEXT_SUMMARY_INPUT_CHARS 2400 Max chars fed into model-backed summarisation
LUMMY_AGENT_MAX_STEPS 4 Max agent loop turns per run
LUMMY_AGENT_MAX_TOTAL_TOKENS 16000 Total token budget across full run
LUMMY_AGENT_MAX_COST_MICROS 5000000 Estimated cost budget across full run
LUMMY_AGENT_MAX_DURATION_MS 60000 Wall-clock budget across full run
LUMMY_AGENT_TOOL_TIMEOUT_MS 5000 Per-tool timeout
LUMMY_AGENT_TOOL_MAX_RETRIES 1 Retry count for retryable tool failures
LUMMY_AGENT_TOOL_FILE_ROOT . File tool sandbox root
LUMMY_AGENT_TOOL_SHELL_WORKING_DIRECTORY . Working directory for shell_command
LUMMY_AGENT_TOOL_NOTES_PATH .lummy_agent_notes.md Durable notes-memory file
LUMMY_WEB_SEARCH_ENGINE tavily Web search engine: tavily, brave, or kagi
LUMMY_TAVILY_API_KEY `` Tavily API token for web_search (Authorization: Bearer ...)
LUMMY_TAVILY_BASE_URL https://api.tavily.com Tavily API base URL
LUMMY_TAVILY_SEARCH_COUNT 10 Number of Tavily search results requested
LUMMY_BRAVE_API_KEY `` Brave Search API subscription token for web_search (X-Subscription-Token)
LUMMY_BRAVE_BASE_URL https://api.search.brave.com/res/v1 Brave Search API base URL
LUMMY_BRAVE_SEARCH_COUNT 10 Number of Brave search results requested (clamped to 1-20)
LUMMY_KAGI_API_KEY `` Kagi API token for web_search (Authorization: Bot ...)
LUMMY_KAGI_BASE_URL https://kagi.com/api/v0 Kagi API base URL
LUMMY_KAGI_SEARCH_LIMIT 10 Number of Kagi search results requested
LUMMY_AGENT_ALLOWED_TOOLS calculator,web_fetch,web_search,file_read,file_write,shell_command,notes_memory Comma-separated tool allowlist
LUMMY_AGENT_ALLOWED_TOOL_CAPABILITIES math,filesystem_read,network,external_api,memory Default capability allowlist; blocks file_write and shell_command by default
LUMMY_AGENT_MAX_TOOL_RISK low Global tool risk ceiling
LUMMY_AGENT_EXTERNAL_PROVIDER_MAX_TOOL_RISK low Extra risk ceiling applied when non-basic-local provider is in chain
LUMMY_SKILLS_PATH ./skills Directory of local Agent Skills packages (<name>/SKILL.md) used by explicit skill_ids
LUMMY_AGENT_SKILL_DISCOVERY_PATH ./skills Directory scanned for discovered slash/model-visible skills; relative paths resolve under LUMMY_AGENT_TOOL_FILE_ROOT
LUMMY_MCP_SERVERS_PATH ./mcp_servers.json Local MCP server config file

Dev

gleam format --check src && gleam check
cd agent && gleam format --check src test && gleam check && gleam test
cd server && gleam format --check src test && gleam check && gleam test
cd shared && gleam format --check src test && gleam check && gleam test
cd ui && gleam format --check src test && gleam check && gleam test

Windows + Zig note:

cd agent && bash ../scripts/with-zig-env.sh gleam test

Or from cmd.exe / PowerShell in project root use bundled rebar3.cmd wrapper.

Docs

  • Plan: PLAN.md
  • Active implementation checklist: TODO.md
  • Bootstrap notes: docs/bootstrap.md
  • Domain core notes: docs/domain-core.md
  • Persistence notes: docs/persistence.md
  • Session runtime notes: docs/session-runtime.md
  • Skills v1: docs/skills.md
  • MCP v1: docs/mcp.md
  • Quickstart: docs/quickstart.md
  • HTTP API notes: docs/http-api.md
  • Event stream notes: docs/event-stream.md
  • Event log notes: docs/event-log.md
  • Changelog: CHANGELOG.md

About

Lummy Agent

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages