A collection describes itself, so an agent reads and writes it as a person does, through the same checks.
The description
Cardbox.describe(tasks)%{
"name" => "Tasks",
"fields" => [
%{"key" => "title", "name" => "Title", "type" => "text", "operators" => ["eq", "neq", "contains", ...]},
%{"key" => "status", "name" => "Status", "type" => "status",
"choices" => [%{"name" => "Not started", "group" => "todo"}, ...],
"operators" => ["eq", "neq", "in", "not_in", "in_group", "is_empty", "is_not_empty"]},
%{"key" => "updated", "name" => "Updated", "type" => "updated_at", "read_only" => true, ...}
]
}Give it to a model as the description of a tool, with the query format of Queries.
The values, as a JSON Schema
Cardbox.json_schema/1 says what a record's values are, by field key, as
JSON Schema: a choice as one of its ids (with
its name as its title), a day as a date, a relation as record ids, and
null for each, which empties it. It is the input_schema of a tool that
adds or changes records, or the schema of a model's structured output:
%{
name: "add_task",
description: "Adds a task.",
input_schema: Cardbox.json_schema(tasks)
}Cardbox takes more than the schema says (a choice's name, a number as text), and checks every value as it writes it.
Tools
Three tools cover most of what an agent does: one that lists records, one that adds them, one that changes them.
def call("list_records", %{"collection" => id, "query" => query}, scope) do
collection = Box.get_collection!(id)
with {:ok, records} <- Box.list(collection, query, me: scope.agent, limit: 50) do
{:ok, Enum.map(records, &Cardbox.Record.to_map(&1, collection))}
end
end
def call("update_record", %{"collection" => id, "id" => record, "values" => values}, scope) do
Box.update(Box.get_collection!(id), record, values, actor: scope.agent)
endAn agent names fields and choices as people see them ("Status", "In
progress"); Cardbox reads them as keys and ids. An agent's write is
checked as a person's, against its fields and your allow?/3, and each
record keeps the agent as who changed it (actor_id/1).
Errors are instructions
Every error says what to write instead: the fields a collection has, the
operators a field takes, the choices of a select, the form of a day.
Give the error's message back to the model, and it corrects itself.
Changing the schema
add_field/3, update_field/4 and delete_field/3 are tools too: an
agent that files emails into a collection adds the field it needs. Keep
them behind allow?/3 (:change_fields) for the agents you trust with
them.