diff --git a/Agents.md b/Agents.md index 0ec20f2..1825d13 100644 --- a/Agents.md +++ b/Agents.md @@ -4,6 +4,12 @@ Agents in Loki follow the same style as OpenAI's GPTs. They consist of 3 parts: * [RAG](RAG) - Pre-built knowledge bases specifically for the agent * [Function Calling](Tools#tools) ([#2](MCP-Servers)) - Extends the functionality of the LLM through custom functions it can call +> **Looking for declarative, multi-step workflows?** See +> [Graph Agents](Graph-Agents): a YAML-driven workflow engine where each step +> (LLM call, script, user prompt, child-agent spawn) is its own typed node. +> Useful when an agent's behavior follows a fixed shape rather than a single +> open-ended LLM loop. + ![Agent example](./images/agents/sql.gif) Agent configuration files are stored in the `agents` subdirectory of your Loki configuration directory. The location of @@ -738,3 +744,8 @@ Loki comes packaged with some useful built-in agents: * `oracle`: An agent for high-level architecture, design decisions, and complex debugging * `sisyphus`: A powerhouse orchestrator agent for writing complex code and acting as a natural language interface for your codebase (similar to ClaudeCode, Gemini CLI, Codex, or OpenCode). Uses sub-agent spawning to delegate to `explore`, `coder`, and `oracle`. * `sql`: A universal SQL agent that enables you to talk to any relational database in natural language + +Loki writes these built-in agents to your agents directory on first run and never overwrites them afterward, so any +edits you make to them are preserved across Loki updates. To discard your local changes and reinstall the built-in +agents from the current Loki build, run `loki --install agents` (or `.install agents` in the REPL). Agents you created +yourself are not affected. diff --git a/Graph-Agents.md b/Graph-Agents.md new file mode 100644 index 0000000..a4a9079 --- /dev/null +++ b/Graph-Agents.md @@ -0,0 +1,950 @@ +Graph-based agents are a declarative, YAML-driven workflow engine layered on +top of Loki's existing agent system. Where a normal [agent](Agents) runs as a +single LLM loop driven by tool calls, a **graph agent** is a directed graph of +typed nodes. Each node performs one well-defined step (call an LLM, run a +script, ask the user a question, spawn a child agent, etc.) and routes to the +next node based on its result. + +Graph agents are best for workflows that: + +- Have a fixed shape (e.g. parse -> query -> grade -> synthesize -> verify) +- Mix LLM calls with deterministic steps (scripts, user prompts) +- Need explicit human-in-the-loop checkpoints +- Benefit from per-step model / tool / temperature overrides + +If you just want an agent that takes a goal and figures out the steps on its +own, stick with a regular [agent](Agents). + +--- + +# Directory Structure + +A graph agent is defined by a single `graph.yaml`. It holds *both* the +agent-level config (model, tools, MCP servers) *and* the workflow: + +``` +/agents + └── my-graph-agent + ├── graph.yaml # agent config + workflow definition + ├── tools.sh # optional custom tools + ├── .yaml # auto-built knowledge base for a rag node + └── scripts/ # optional script-node implementations + ├── decide.py + └── verify.py +``` + +`.yaml` files are generated by Loki at agent load time - one +per `rag` node - and should not be hand-edited. + +An agent directory must contain **either** a `config.yaml` (a normal, +LLM-loop agent (see [Agents](Agents))) **or** a `graph.yaml` (a graph +agent). Never both. The presence of `graph.yaml` is what marks an agent +as a graph agent; when Loki runs it, execution is driven entirely by the +graph. + +**Both files present is an error.** If an agent directory contains both +`config.yaml` and `graph.yaml`, Loki refuses to load it and tells you to +remove one. Pick the model that fits: `config.yaml` for an open-ended +LLM-loop agent, `graph.yaml` for a fixed-shape workflow. + +--- + +# graph.yaml Top-Level Fields + +```yaml +name: my-graph-agent +description: | + Plain prose describing what the workflow does. +version: "1.0" + +# --- agent-level config --- +model: anthropic:claude-sonnet-4-6 # default model for llm nodes +temperature: 0.0 # default sampling temperature +top_p: null # default sampling top-p +global_tools: # global tools available to nodes + - web_search_loki.sh +mcp_servers: # MCP servers available to nodes + - pubmed-search +conversation_starters: # suggested prompts in the UI + - "Look up LOINC 2160-0" + +settings: + max_loop_iterations: 100 # PER-NODE visit cap; default 100 (see below) + log_state_snapshots: true # log state JSON before each node executes + validate_before_run: true # run the graph validator on startup + timeout: 600 # optional overall timeout in seconds + +initial_state: # optional seed state for the run + topic: "auth" + +start: parse_input # required: ID of the first node to run + +nodes: + parse_input: { ... } + ... +``` + +- **`version`:** Currently only `"1.0"` is accepted by the parser. Anything + else fails at startup. This is the *graph schema* version, not your + agent's version. +- **Agent-level config** (`model`, `temperature`, `top_p`, `global_tools`, + `mcp_servers`, `conversation_starters`) are all optional. + These are the same fields a normal agent's `config.yaml` carries; in a + graph agent they live at the top of `graph.yaml` instead. `model` / + `temperature` / `top_p` act as the defaults for `llm` nodes that don't + set their own. `global_tools` and `mcp_servers` define the tool universe + that an `llm` node's `tools:` whitelist selects from (a node with no +`tools:` field gets none of them). +- **`can_spawn_agents` is derived, not declared.** A graph agent can spawn + child agents iff its graph contains at least one `agent` node. You don't + set a flag. The `agent` node's presence *is* the declaration. +- **`max_loop_iterations`:** This is a **per-node visit cap**, not a total + graph-step cap. If the same node id is entered more than this many times, + execution aborts with `Node 'X' visited N times (max_loop_iterations=...)`. + Default: 100. +- **`timeout`:** Wall-clock cap on the entire graph run. The executor + checks this between every node transition; nodes that block longer than + the timeout will still finish before the check fires. +- **`initial_state`:** A JSON-compatible object. Values are seeded into + state before any node runs and are referenced from any node via `{{key}}` + templates. + +### `{{initial_prompt}}`: Automatically Seeded + +When Loki invokes a graph agent with a user prompt (whether from the +command line `loki -a my-agent "what is X?"`, from the REPL, or from a +parent agent that spawned it as a sub-agent), the dispatcher automatically +seeds the prompt text into state under the key **`initial_prompt`** before +any node runs. + +This means every graph agent's first node can reference the user's request +via `{{initial_prompt}}`: + +```yaml +parse_input: + id: parse_input + type: llm + prompt: "{{initial_prompt}}" # the user's command-line / REPL text + ... +``` + +You do not need to (and should not) put `initial_prompt` in `initial_state` as it is overwritten by the dispatcher. + +--- + +# Node Types + +There are seven node types: **agent**, **script**, **approval**, **input**, +**llm**, **rag**, and **end**. Every node has these common fields: + +```yaml +my_node: + id: my_node # must match the map key + type: + description: optional # free-form + next: another_node # optional default next node; semantics vary per type +``` + +The `next` field defines the default routing edge. Node types interpret it +differently (some types ignore it in favor of internal routing; see each type +below). + +--- + +## agent + +Spawns a Loki sub-agent and waits for it to finish. This is how a graph agent +delegates a sub-goal to a fully autonomous Loki agent (with its own tool loop +and configuration). + +```yaml +research_topic: + id: research_topic + type: agent + agent: deep-researcher # name of an existing Loki agent + prompt: "Research {{topic}}" # interpolated against state + timeout: 600 # optional, in seconds (default 300) + state_updates: + findings: "{{output}}" + output_schema: { ... } # optional, see "Structured Output" below + next: render +``` + +- **`agent`:** Name of the child agent to spawn. Must exist in + `/agents/`. +- **`prompt`:** The user message sent to the child agent. Templated against + the current graph state. +- **`timeout`:** Hard wall-clock cap. If the child agent exceeds it, the + whole graph fails (no built-in fallback path on agent nodes). +- **`state_updates`:** Map of `state_key: "{{template}}"`. The child agent's + final text is available inside this map as `{{output}}`. + +--- + +## script + +Runs a Bash, Python, or TypeScript script and merges its JSON-object stdout +into state. Script files live under the agent's `scripts/` directory. + +**Supported extensions and runtimes**: + +| Extension | Runtime invoked | Notes | +|-----------|----------------------------|-----------------------------------------| +| `.sh` | `bash