Skip to content
LogoLogo

Actions

An action is a typed write into a source dataset. It's how new rows reach your data through the same checkpointed path the agent edit loop uses: validated before anything touches disk, snapshotted before the write, reversible after. You declare an action in the manifest; an agent runs it over MCP.

Loading diagram...

What an action can write

Only a source dataset. A file-backed dataset (CSV, JSON, JSONL) or a SQLite table takes inserts. A derived sql dataset never does, because it's a query, not a file. Pointing an action at a sql dataset is a named validation error.

Declaring one

An action names its target dataset and a mode. The only mode today is insert, which appends rows:

"actions": {
  "log_meal": {
    "dataset": "meals",
    "mode": "insert"
  }
}

That alone inserts rows whose columns match the meals data. Most actions add a fields block to constrain or annotate specific columns.

The row schema

An action carries no hand-written schema. It derives one from the live data: the compiler infers each column's type from the dataset, then layers your fields overrides on top. The result is a strict schema (unknown columns are rejected) that an agent reads before it sends a single row.

A fields.<column> entry narrows one column:

"actions": {
  "log_meal": {
    "dataset": "meals",
    "mode": "insert",
    "fields": {
      "meal_type": { "enum": ["breakfast", "lunch", "dinner", "snack"] },
      "calories":  { "type": "number", "min": 0 },
      "source":    { "default": "manual" }
    }
  }
}

Each override key does one job:

  • type: constrain the column to string, number, boolean, or date, overriding what was inferred.
  • enum: restrict a string column to a fixed set of values.
  • min / max: numeric bounds.
  • default: a value applied when a row omits the column.
  • description: a note that rides along with the schema, so the agent knows what the column means.

A key in fields that doesn't match a real column is an error. You can only override columns that exist.

What happens on a write

An agent calls run_action(name, rows). Before a single byte lands:

  • Every row is validated against the resolved schema. One bad row (wrong type, a value outside min/max, an unknown column) rejects the whole call with an error naming the row index and the field, and nothing is written.
  • The target file is snapshotted to .openislands/history/, so the insert is reversible with rollback.

Then the rows land: appended to a flat file (a new CSV row, a push onto a JSON array, a line added to NDJSON) or INSERTed into the SQLite table. A SQLite insert needs the file and table to exist already; a flat file is created if it's missing.

Inserts are all-or-nothing and path-confined: an action can only write the one source file its dataset names. There is no general file write.

Running an action

Actions belong to the agent edit loop, not to a CLI command. An agent:

  1. Calls list_actions to get each declared action and its resolved row JSON Schema (the live schema merged with fields). That schema is its grounding for a valid row.
  2. Calls run_action(name, rows), which validates and inserts as above, returning the count inserted and a checkpoint_id.

Surfacing an action to humans

An action isn't agent-only. Drop a form.entry island on a page and point it at an action, and the runtime renders a form — one typed input per field, the action's types, enums, ranges, and defaults carried straight through — with a submit button that inserts a row. It runs the very same write path run_action does: validate, snapshot, insert, then the bound dataset's islands refresh live. The form is the human-facing mirror of the agent call, authored by reusing the action rather than re-declaring its fields.

See MCP Server for the full tool surface, and Connectors for syncing a provider's data into source datasets on a schedule, through this same write path.