Overview
Agent is an autonomous ops helpdesk: you give it a free-text question about your systems, and it investigates using your own service registry, tickets, runbooks, and long-term memory, then either resolves the situation itself (tier-1) or opens an approval and waits for a human (tier-2). It ships as two independent pieces:
- agent-rs — the Rust core engine. Use it as a standalone HTTP service (
agent-server), or embed it directly in another Rust program via theagent-sdkcrate. - agent-go — a Go client library plus
agentctl, a CLI for scripting against a running instance.
Quick start
Requires a Rust toolchain (1.75+) and, optionally, Go (1.21+) if you want the CLI.
cp .env.example .env # edit .env — set GROQ_API_KEY or OPENAI_API_KEY cd agent-rs cargo run -p agent-server # listening on 0.0.0.0:8000
From another terminal:
curl http://localhost:8000/services
curl -X POST http://localhost:8000/query \
-H 'Content-Type: application/json' \
-d '{"query": "is payments healthy?"}'
Configuration
Every setting is an environment variable — no config file to manage.
| Variable | Default | Meaning |
|---|---|---|
| GROQ_API_KEY | — | Checked first. Enables the LLM reasoner. |
| OPENAI_API_KEY | — | Used if GROQ_API_KEY isn't set. |
| LLM_MODEL | openai/gpt-oss-20b | Model name sent to the chat-completions endpoint. |
| LLM_BASE_URL | Groq's endpoint | Any OpenAI-compatible base URL. |
| DATABASE_PATH | devops_agent.db | SQLite file. Use :memory: for ephemeral runs. |
| KNOWLEDGE_DIRS | knowledge_sources:runbooks | Colon-separated dirs scanned for JSON/CSV/Markdown at startup. |
| BIND_ADDR | 0.0.0.0:8000 | agent-server only. |
| ALLOWED_ORIGINS | (none) | agent-server only. Comma-separated extra CORS origins allowed to call the API directly from a browser, e.g. http://localhost:3000,https://my-frontend.example.com. |
| ALLOW_ANY_ORIGIN | false | agent-server only. Set true to accept requests from any origin — convenient for a public demo, not recommended once real credentials are involved. |
API reference
All request/response bodies are JSON.
| Path | Method | Notes |
|---|---|---|
| /healthz | GET | Liveness check. |
| /services | GET | List every registered service. |
| /tickets | GET | List every ticket. |
| /tickets/{id} | GET | Fetch one ticket. |
| /query | POST | Body: {"query": "..."}. Returns the tiered decision. |
| /query/{id} | GET | Fetch a previously submitted query's status. |
| /approvals | GET | List pending (undecided) approvals. |
| /approvals/{id} | GET | Fetch one approval's full detail. |
| /approvals/{id}/decide | POST | Body: {"decision": "approve"|"reject", "approver": "name"}. |
| /audit-logs | GET | Most recent audit trail entries. |
| /memory | GET / POST | List, or add, a long-term memory fact. |
| /memory/{id} | GET | Fetch one memory fact. |
Embedding directly (Rust)
Skip HTTP entirely and call the engine as a library:
use agent_sdk::{Agent, AgentConfig};
let agent = Agent::bootstrap(AgentConfig::from_env()).await?;
let response = agent.submit_query("is payments healthy?").await?;
if let Some(approval_id) = response.approval_id {
agent.decide_approval(approval_id, "approve", Some("alice"))?;
}
The AgentConfig builder lets you override the database path and knowledge directories without touching environment variables:
let config = AgentConfig::default()
.with_database_path(":memory:")
.with_knowledge_dirs(vec!["./my-runbooks".into()]);
Go CLI reference (agentctl)
agentctl health agentctl services agentctl tickets agentctl query "<free-text query>" agentctl query-status <id> agentctl approvals list agentctl approvals get <id> agentctl approvals decide <id> --decision approve|reject [--approver <name>] agentctl memory add --source <src> --fact "<fact>" [--context "<ctx>"] agentctl memory list agentctl audit-logs agentctl wait [--timeout 30s]
Every command accepts --url, or reads $AGENT_URL. Output is pretty-printed JSON on stdout; failures exit non-zero with the backend's error message on stderr — safe to use in shell scripts and CI gates.
Deployment
Docker (backend + frontend together):
docker compose up --build
Bare binary + systemd: build with cargo build --release -p agent-server, copy the binary to the target host, and use the unit file in deploy/systemd/agent-server.service as a starting point — it includes an agentctl wait pre-start check.
Behind a reverse proxy: the server has no built-in TLS termination; put it behind nginx/Caddy/your load balancer, same as you would any other backend service.
Frontend: the frontend/ directory is now a small Express app (frontend/server.js), not a static-only site. Run npm install && npm start inside it, or let docker compose build it. It serves the site and proxies live-demo API calls to whichever backend URL is configured — see frontend/.env.example. Because the proxy hop happens server-to-server, it works against any deployed agent-server URL without needing that server's CORS list to know about this frontend's origin ahead of time.
Cross-origin access from other frontends: if you build a different UI that calls agent-server directly from the browser (rather than through the proxy above), add its origin to ALLOWED_ORIGINS (comma-separated) when starting agent-server, or set ALLOW_ANY_ORIGIN=true for a public demo deployment where every origin should be allowed.
FAQ
Does this replace my SQLite database from the old Python service?
No migration needed — same schema, same tables. Point DATABASE_PATH at your existing file, or start fresh.
What happens if the LLM key is missing?agent-server refuses to start. agent-sdk, embedded, returns a clear error from submit_query instead — your calling code decides what to do.
Can I use a local model?
Yes — set LLM_BASE_URL to any OpenAI-compatible endpoint (vLLM, Ollama's OpenAI shim, etc).
How do I improve retrieval quality?
The default embedder is a dependency-free lexical fallback. Implement the Embedder trait in agent-core::retrieval to plug in a real embedding model — one function, no other code changes.