oto_fleet
Declared configuration of an agent PASS — what a fleet runs, on which table, within which perimeter, and up to which limit. op=create (label + procedure slug + tools allowlist ; optional target namespace + row_filter, execution context model — REQUIRED, one of the catalogue served as runner.models by oto_trigger (refused model_required when omitted: the pass runs on its organization's model key); provider is deduced from it —, and limits max_rows / max_tokens / max_consecutive_failures / max_tokens_per_row — budgets are counted in TOKENS, never money) / list (optionally filtered by status; one CARD per fleet — input_sha256 in place of input, and projection names what it leaves out) / get (the full declaration) / state / update. ⚠️ op=launch ARMS the fleet — it does NOT start any process. The state becomes armed, never running: running is a FACT, not an intent — a worker asked for work and Oto produced this pass's first job. That happens BY ITSELF, usually within seconds: workers poll continuously and Oto makes the work when they ask. No scheduler is involved and none has to be started. An armed still armed after a minute therefore means NO WORKER IS POLLING for this org — not 'nobody has taken it'. Symmetrically, op=stop REQUESTS the stop (stopping); the fleet keeps reserving, calling and SPENDING until none of its jobs is left in flight, at which point Oto states the fact (stopped) at the next poll. Never report a launch on armed, nor a stop on stopping — the gap between the two is also the diagnosis. op=launch is REFUSED with no_runner_armed when no worker polls for this org at all (nothing would ever execute it — reading, updating and op=stop stay open), and with model_not_served when the fleet declares a model no live worker serves: its jobs would wait forever — and model_required for a fleet declared without a model (declare another one). op=state returns the pass PROGRESS aggregated over its jobs — pending, claimed, done, failed, abandoned, tokens consumed, heaviest single row, empty_jobs (finished with no row), stopped_after_write — plus reservable_rows, what the scheduler still sees to serve — and says no_jobs_attached explicitly rather than returning zeros you would read as 'nothing happened'. ⚠️ Those counters count JOBS, never rows: a job that gives its row back unwritten still ends done, so done = jobs_total with failed = 0 is NOT a success. What became of the ROWS is in rows — per final status value, concluded, abandoned (failed, with the reason) and open, read from the table itself — and what each execution concluded is in runs_by_outcome (done / partial / failed / blocked). Report the outcome from rows, and say its scope. The TARGET is frozen at declaration: redirecting a running pass to another table is what declaring exists to prevent; the execution context (provider/model) is frozen too, since changing it mid-flight falsifies the attribution of rows already written — declare another fleet instead — duplicate, never switch. An EXTERNAL scheduler is still served here, and each of its calls names it with taken_by (its own id, stable across its restarts): op=take (armed→running, records taken_by — returned by get and list; taking back a running pass it already holds is a resume and succeeds, one held by ANOTHER scheduler is refused held_by_other), op=beat (heartbeat AND reads back stop_requested in the same call), op=ack_stop (stopping→stopped) — both refused not_the_holder to anyone but the holder. ⚠️ None of them is required any more, and you should not call them: polling alone moves a pass, and op=take would only claim one that was about to start on its own.
Tool call template
Replace each placeholder with a value matching the input schema. Only required parameters are included; add optional parameters as needed.
{
"id": 1,
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"arguments": {
"op": "<op>"
},
"name": "oto_fleet"
}
}Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| op | Yes | ||
| _org | No | ||
| input | No | ||
| label | No | ||
| model | No | ||
| tools | No | ||
| reason | No | ||
| status | No | ||
| _run_id | No | OBLIGATOIRE à chaque appel dès `run_start` ou `data_claim_next` : sans lui, l'écriture sur une ligne réservée est refusée. Hérité seulement si le serveur tient un run actif — n'y compte pas. | |
| workers | No | Cap on this campaign's jobs in flight (pending + claimed), default 1. A ceiling, not a head count: the runners polling the queue decide how many actually run (see `runner.workers` on triggers). | |
| fleet_id | No | ||
| max_rows | No | ||
| provider | No | ||
| taken_by | No | take/beat/ack_stop (required): the scheduler's own identifier — stable across its restarts, distinct from any other scheduler. | |
| max_steps | No | ||
| namespace | No | create: the target table, by id or name. Resolved ONCE, at declaration, in the declarer's scope, and kept as the table's id — served back in `namespace`. A name that several tables carry at the same rank is refused (`datastore_ambigu`). | |
| procedure | No | ||
| max_tokens | No | ||
| project_id | No | ||
| row_filter | No | ||
| temperature | No | ||
| max_run_seconds | No | Per-row time limit, 60-3600 s. `0` = runner default. | |
| max_tokens_per_row | No | ||
| descriptions_outils | No | What the agent reads of each tool description: {defaut: chars served per description (≥ 1), entieres: [tools served uncut]}. Frozen at creation; omitted, the worker's default applies. | |
| max_consecutive_failures | No |