Skip to content

vlune/selena

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

16 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

License: Apache-2.0

Selena

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.

// agent.json — this is the whole agent
{
  "name": "code-helper",
  "provider": "ollama",
  "model": "qwen3.5:2b",
  "system_prompt": "You are a terse coding assistant.",
  "tools": ["bash", "read_file", "grep"]
}
> 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.

Who Selena is for

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.


What Selena is — and isn't

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

How it compares

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.


Requirements

  • 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, and mistral-nemo are reasonable choices. (The default config names qwen3.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 on PATH (Git Bash / WSL) or rely on custom command-tools, which use PowerShell on Windows. See Limitations.


Quick Start

Assumes Ollama is installed and running.

1. Pull a tool-calling model

ollama pull qwen2.5-coder:7b   # any tool-calling model works

2. Build Selena

git clone https://github.com/vlune/selena.git
cd selena
cargo build --release

The binary lands at target/release/selena. The first build compiles all dependencies and can take several minutes; later builds are incremental.

3. Run it

./target/release/selena

No 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:

>

4. Give it a task

> 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.


What just happened

Selena ran a loop between the model and your machine:

  1. You sent a message.
  2. The model decided it needed to run a command to answer.
  3. Selena ran the command on your machine and captured the output.
  4. The output went back to the model.
  5. 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.


Built-in tools

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.

bash runs arbitrary shell commands. Only enable it where that is acceptable, and read docs/SECURITY.md before combining it with auto_accept.


Memory

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.


Custom tools

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 it

Run 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.


Configuration

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. With bash enabled, the model can run any shell command unprompted. Set auto_accept: false to block tools that declare require_confirmation. See docs/SECURITY.md.

Every field, type, and default: docs/CONFIG.md.


Limitations

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.

For Rust developers

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.


Roadmap (short)

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.


Documentation

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

License

Licensed under the Apache-2.0 License.

About

Agent orchestration framework that manages AI workflows through configuration instead of code. Define tools, memory, providers, and execution flow with portable JSON configs.

Topics

Resources

License

Contributing

Security policy

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages