OTO
concepts · from ARCHITECTURE.md at v0.6.1

Actions: the graph's hands, described and never invoked

the graph declaresan action in MCP tool shape, bound to a subject's fields
→
the caller confirmsby name, before anything changes the world
→
the caller invokesthrough the declared transport, with its own tools
→
the response is recordedas a run, a source document
→
the gatesthe result enters the graph as evidence
Oto never invokes anything. It describes the hands and records what they did.

An action is a file under actions/, one per action, in the shape an MCP server describes a tool (label, description, input schema, annotations) plus what MCP lacks: the class it acts on (subject), how its inputs are filled from the subject's own fields (bind), the preconditions under which it is ready in the rules' pattern language (when, whose first pattern binds the subject), the declared way to invoke it (invoke: an MCP server and tool, a CLI command, an HTTP endpoint or a script path), the names of the environment variables the caller must hold (needs, never values), how a recorded response becomes knowledge (result: a document to the inbox or a proposal), and an optional schedule, allowed on a read-only action only. actions/model.py reads and checks them; oto actions check and oto actions list are the commands.

At build time each action becomes an Action node whose source is its file, with an executed_by edge to the accountable team; graph.json is never written, and the node carries the whole definition in its attributes, so a store served without its project still knows the catalogue. actions/catalogue.py computes readiness: an action's when is matched with the rules' matcher over current and intended facts, the first pattern binds the subject, and the answer is the action as an MCP tool definition (name, description, input schema, annotations) plus an oto block: the subject class, the entities it is ready on or why none, the inputs bound from one entity and the ones the caller must supply, the declared invocation, the variable names it needs, what a recorded result would assert.

The fourteenth tool, kg_actions (on both stores, in the equivalence battery, GET /api/actions, oto query actions), and oto actions list [--ready] / show <id> --on <entity> answer it; the CLI reads the authored graph, the tool reads the store, both through the same functions.

The caller invokes, then hands the response to oto actions record <id> --on <entity> --by <who> --response <file>: actions/runs.py writes the run record under runs/actions/<id>/<stamp>/ (what was invoked, on what, by whom, when, the bound inputs, the response and its hash; the names of the variables it needed, never values), the run as a source document in the inbox, and, for a proposal result, the proposal the then clause renders with $response.<path> values, evidence pointing at the run document.

From there the ordinary path: oto ingest brings the document into the corpus, oto curate add, check, apply promote the fact, oto build compiles it. An intended fact becoming current this way is a recorded change, not a contradiction; an attribute the graph did not have is added, one whose value differs is refused at merge and needs supersession.

At build each Action node gets an acts_on edge per entity a run touched and its last_run, which the catalogue, the explorer and the reader show.

A read-only action may carry a schedule (hourly, daily, weekly): it is due when it never ran or its last run is older than the interval, and oto actions list --due (and kg_actions with due) lists the due ones that are ready on at least one entity.

Nothing that changes the world is ever due. In repository mode oto init --repo writes oto-actions.yml, a workflow on a daily cron, and the script it runs, .github/scripts/oto-actions-invoke.py: the script is the caller.

It asks Oto what is due, binds the inputs, invokes over the declared transport (http with placeholders URL-encoded and header values that name environment variables taken from the environment; cli with inputs shell-quoted; script with the inputs on stdin; mcp skipped, because an MCP tool needs an agent), and records the responses through oto actions record.

The workflow then ingests, takes the proposals through the gates, applies when the check is clean, builds, and opens a pull request: the merge is the human gate. Every variable an action needs is a repository secret of the same name, listed in the workflow's env; values never reach a record or a log, a missing one skips the action and names it, responses are read up to a megabyte and a record keeps at most two, a timeout on the action bounds the call.

Nothing in the engine executes an action, so the read-only serving invariant holds without exception. Action, acts_on and executed_by live in oto-core, as does the status intended: a fact asserted as a plan and not yet observed, which the tools print as such, the apps draw dotted, and no "what is" answer includes.

The design is in plans/actions.md.