Build a real agent in one JSON file. Tools, memory, sub-agents, and behavior — all declared, no code to write or compile.
Selena is a local-first agentic runtime. You describe an agent in agent.json, point it at a model you already run with Ollama or llama.cpp, and Selena drives the loop: the model calls tools, reads the results, and keeps going until the task is done.
No cloud. No API key. No data leaves your machine — the only network traffic is between Selena and your local model server.
> find all TODO comments in the codebase and summarise them
[tool: bash]
[tool done: bash ok]
Found 14 TODOs across 6 files. Most are in auth.rs and relate to
token expiry handling. Two are marked urgent.
You run a model locally and you want it to do things — run commands, read and write files, call your scripts — without standing up a Python project to glue it together. You tried a framework, and maintaining agent logic spread across a dozen source files got old.
With Selena, the entire agent lives in one JSON file. Change the model, swap the tools, add a sub-agent, rewrite the system prompt — edit JSON and run again. There is nothing to recompile.
If you're a Rust developer, the same engine ships as a library (selena-core) you can embed directly. That's a secondary path — see docs/EMBEDDING.md.
Selena is a runtime: a single binary that reads a config and runs an agent loop against a local model.
It is not:
- a chat UI or a coding IDE plugin
- a cloud product or hosted service
- a Python framework or a LangChain port
- a model server — it sits on top of Ollama or llama.cpp, it does not run the model itself
- production-hardened for multi-user or multi-tenant deployments
The honest alternatives for the "local model that uses tools" problem:
| Selena | Python frameworks (LangChain / LlamaIndex) | Raw Ollama API loop | |
|---|---|---|---|
| Where the agent lives | One JSON file | Spread across Python source files | Your own code |
| Code to write | None | Glue, classes, wiring | All of it |
| Tool calling loop | Built in | Built in | You write it |
| Add a tool | Drop a script + manifest in a folder | Write a Python class | Hand-wire it |
| Session memory & sub-agents | Built in | Varies by framework | You build it |
| Runs offline / local-only | Yes, by design | Usually, with setup | Yes |
| Language / runtime | Single Rust binary | Python environment | Whatever you wrote |
Python frameworks target a different ecosystem and are far broader; Selena is narrower on purpose. If you want declarative, local, no-code agents, that narrowness is the point.
- A local model server: Ollama or a llama.cpp server.
- A model that supports tool calling. This matters — Selena relies on the model emitting tool calls.
qwen2.5-coder,llama3.1, andmistral-nemoare reasonable choices. (The default config namesqwen3.5:2b; use whatever tool-capable model you have pulled.) - The Rust toolchain (1.75+) to build from source. There are no pre-built binaries yet — step one is a
cargo build.
The test suite passes on Linux, macOS, and Windows. Two built-ins (
bash,grep) shell out to Unix tools not present on a stock Windows install — provide a Unix toolchain onPATH(Git Bash / WSL) or rely on custom command-tools, which use PowerShell on Windows. See Limitations.
Assumes Ollama is installed and running.
ollama pull qwen2.5-coder:7b # any tool-calling model worksgit clone https://github.com/vlune/selena.git
cd selena
cargo build --releaseThe binary lands at target/release/selena. The first build compiles all dependencies and can take several minutes; later builds are incremental.
./target/release/selenaNo config file is required for the first run — Selena falls back to a built-in default that targets Ollama on localhost:11434. You'll get a bare prompt:
>
> list the files in this directory and tell me which ones are Rust source files
[tool: bash]
[tool done: bash ok]
The directory contains Cargo.toml, agent.json, a crates/ directory, and a
target/ directory. The Rust source files live under crates/.
Type /quit or /exit to leave (or press Ctrl-D). Ctrl-C cancels the current turn without exiting.
Selena ran a loop between the model and your machine:
- You sent a message.
- The model decided it needed to run a command to answer.
- Selena ran the command on your machine and captured the output.
- The output went back to the model.
- The model answered based on what it saw.
That cycle — model requests a tool, Selena runs it, result returns to the model — repeats until the model is done or the iteration limit is hit. You never paste output back yourself.
Enable tools by listing them in the tools array. The defaults are:
| Tool | What it does |
|---|---|
bash |
Run a shell command, return stdout/stderr (uses sh; Unix-oriented) |
read_file |
Read a file's contents |
write_file |
Write content to a file, creating parent directories |
edit |
Replace an exact string in a file (unique match enforced) |
glob |
List files matching a glob pattern (in-process, cross-platform) |
grep |
Search files for a pattern (shells out to the grep binary) |
webfetch |
Fetch a URL and return it as text (HTML stripped); native, no external runtime |
todowrite / todoread |
Maintain a shared task list for the current session |
store_memory / retrieve_memory |
Save/read a named value in session memory |
remember / recall |
Persist/read facts across runs (project + global scopes) |
Auto-registered when configured: delegate_task / dispatch_parallel (sub-agents), lsp_diagnostics / lsp_hover (LSP), and any MCP server tools.
Full reference, inputs, and outputs: docs/TOOLS.md.
bashruns arbitrary shell commands. Only enable it where that is acceptable, and read docs/SECURITY.md before combining it withauto_accept.
Within a single run, the model can remember things you tell it (store_memory / retrieve_memory):
> remember that the database password is in .env.production
[tool: store_memory]
[tool done: store_memory ok]
Stored.
> where is the database password?
[tool: retrieve_memory]
[tool done: retrieve_memory ok]
You told me it is in .env.production.
That session memory clears when the process exits. For knowledge that should
survive restarts, enable persistent memory (memory.persistent.enabled) and
use the remember / recall tools — they write markdown to a project store
(.selena/memory) or a global store (~/.selena/memory), and the entries
are loaded back into the prompt at startup. See docs/CONFIG.md.
Add a tool by dropping a script and a manifest into a tools/ folder — no change to agent.json.
./target/release/selena tools scaffold weather # generates tools/weather/
# edit tools/weather/weather.sh (or weather.ps1 on Windows)
./target/release/selena tools doctor # validate the manifest + script
./target/release/selena tools trust weather # record its hash and trust itRun Selena again; the model now has a weather tool. Custom tools run as subprocesses that read JSON arguments on stdin and reply on stdout with {"success": true, "output": "..."}.
When you trust a tool, Selena records a SHA-256 hash of its manifest and script. If either changes, trust is automatically revoked on the next startup. Full manifest format and the trust model: docs/TOOLS.md.
Selena reads agent.json from the current directory (override with --config path/to/agent.json). If none is found, a built-in default is used. A fuller example:
{
"name": "my-agent",
"provider": "ollama", // "ollama", "llamacpp", or "custom" (any OpenAI-compatible API)
"model": "qwen2.5-coder:7b",
"system_prompt": "You are a helpful assistant.",
"tools": ["bash", "read_file", "write_file", "glob", "grep"],
"skills": ["store_memory", "retrieve_memory"],
"auto_accept": true, // run tools without per-call confirmation
"inference": {
"endpoint": "http://localhost:11434",
"temperature": 0.7,
"max_tokens": 4096,
"timeout_secs": 120
},
"context": { "max_tokens": 32768, "history_slots": 20, "reserve_output_tokens": 4096 },
"memory": { "max_segments": 50 },
"runtime": { "max_iterations": 20 },
"logging": { "level": "info", "format": "pretty" }
}
auto_accept: true(the shipped default) means tools run without confirmation. Withbashenabled, the model can run any shell command unprompted. Setauto_accept: falseto block tools that declarerequire_confirmation. See docs/SECURITY.md.
Every field, type, and default: docs/CONFIG.md.
Real gaps. Read them before deciding whether Selena fits.
| Limitation | Detail |
|---|---|
| Build from source required | No pre-built binaries. You need Rust installed. First run is a cargo build. |
| No kernel sandbox | sandbox.enabled gives subprocess tools a scrubbed, allowlisted environment (secrets aren't exposed), plus working-dir confinement. But capability labels (network, filesystem, execute) are still not OS-enforced — a tool labelled network: false can reach the network. Kernel isolation (seccomp/landlock, Job Objects) is on the roadmap. |
| Unix-oriented built-ins | bash (sh -c) and grep (the grep binary) assume a Unix environment; on a stock Windows install they need a Unix toolchain on PATH. The test suite passes on Linux, macOS, and Windows; custom command-tools use PowerShell on Windows. |
| Streaming is opt-in | The final answer is buffered by default; set runtime.stream: true for token-by-token output. Streaming recovers tool calls from content, which is lossless for Ollama/llama.cpp but falls back to buffered mode for native-only cloud providers. |
| No GUI | Command-line only. |
selena-core is a library crate with no dependency on the CLI. Embed it directly:
[dependencies]
selena-core = { path = "path/to/selena/crates/selena-core" }use selena_core::{Core, AgentConfig};
let config = AgentConfig::from_json(include_str!("agent.json"))?;
let mut core = Core::with_config(config)?; // construction is synchronous
let result = core.turn("summarise the files in /tmp").await?; // only turn() is async
println!("{}", result.content);Full API, events, custom providers, and registering tools programmatically: docs/EMBEDDING.md.
Recently shipped: live MCP client, generic OpenAI-compatible provider (any API model via a gateway), cross-session persistent memory, streaming turn output, interactive ask permissions, parallel + nested sub-agent dispatch, and LSP tools (lsp_diagnostics / lsp_hover).
Planned: OS-level sandbox enforcement of capability labels, a durable (SQLite/embeddings) persistent-memory backend, and a richer TUI.
Full detail: docs/ROADMAP.md.
| docs/CONFIG.md | Every config field, type, default, and valid value |
| docs/TOOLS.md | Built-in tools, manifest format, trust model |
| docs/PROVIDERS.md | Ollama vs llama.cpp, tool-calling behavior, inspect-provider |
| docs/SECURITY.md | What is and isn't enforced, audit logging, risks |
| docs/EMBEDDING.md | Using selena-core as a Rust library |
| docs/EXAMPLES.md | Copy-paste agent configurations |
| docs/TROUBLESHOOTING.md | Common failures and fixes |
| docs/FAQ.md | Common questions |
| docs/ARCHITECTURE.md | Internal design for contributors |
| docs/ROADMAP.md | Built, in progress, and planned |
| docs/CONTRIBUTING.md | How to contribute |
Licensed under the Apache-2.0 License.