Docs/Learn
API reference

Learn

POST /v1/agents/learn. Turn recorded turns into Simulang code, or repair that code (research preview).

View as Markdown

Turns what Sai did in one or more turns into a Simulang function you own, async function main(params = { ... }), which your program then runs on a computer through direct control. Given code you already have, the same call repairs it. Learned code walks through the whole loop.

Research preview: the request and response may change.

Request

POST /v1/agents/learn
json
{
  "turns": ["turn_vzjz62w7j7jvOilZ6zBL"],
  "template": "Open {{site}} and read the title of the top story"
}
FieldTypeRequiredNotes
turnsarrayyes¹Turn ids from one session, in order, the first one stating the task. Each must have ended and been sent with options.recordTrajectory: true. At most 20.
templatestringnoThe task with {{name}} placeholders; each one becomes a parameter. Default: the first turn's template, else one inferred from the first turn's message.
initialCodestringnoCode to repair. With it the call heals the code; without it, it converts turns. At most 200 KB.
evaluationsarraynoHow initialCode did on runs (below). Needs initialCode. At most 20.
feedbackstringnoYour note for the repair, e.g. "the Save dialog changed". Needs initialCode.
modelstringnoAn id from /models. Default anthropic/claude-opus-5-5. An unknown id is a 400.
idempotency_keystringnoThe same key again returns the earlier result and runs nothing.

¹ Optional when repairing: send initialCode with evaluations, feedback or new turns. New turns are the usual input: the code failed on a case, you sent the same template to POST /message with recordTrajectory, Sai solved it, and that turn shows what the code is missing.

An evaluation:

FieldTypeNotes
inputsobjectThe values the code ran with.
resultobjectThe run's exec result: its result, error and last logs. At most 20 KB; leave the screenshot out.
judgementstringA judgementId from POST /v1/agents/judge; the repair sees its verdict and evidence.
passedbooleanYour own verdict.
notestringWhat went wrong, in your words.

The call does the work before it answers, about two minutes for a conversion. Set your client's timeout to at least 10 minutes. If your side times out, send the same request with the same idempotency_key, or read the result with GET /v1/agents/learn/{learnId}.

Response

200:

json
{
  "learnId": "learn_3Fq9cT1kZ0aJ",
  "mode": "convert",
  "status": "succeeded",
  "title": "Read the top story title",
  "template": "Open {{site}} and read the title of the top story",
  "parameters": [{ "name": "site", "defaultValue": "https://news.ycombinator.com" }],
  "code": "async function main(params = { site: \"https://news.ycombinator.com\" }) {\n  ...\n}",
  "validation": { "errors": [], "warnings": [] },
  "sourceTurns": ["turn_vzjz62w7j7jvOilZ6zBL"],
  "usage": { "inputTokens": 61240, "outputTokens": 3120 },
  "researchPreview": true
}
FieldNotes
modeconvert or heal.
titleA short name for the code, a few words long, written by the model.
statussucceeded, or failed when validation.errors is not empty.
templateThe template used or inferred. Send it to POST /message with options.inputs when the code fails and you want Sai to take over.
parametersThe keys of main's params, with the values from the source turn as defaults.
codeThe function. It uses the bound machine, throws needs_sign_in: <site or app> when a sign-in is needed and precondition_failed: ... when it starts on the wrong screen, and returns the task's answer.
outputSchemaWhen the source turn had an output_schema: what main returns.
validationStatic checks on the code: errors (an unused or unknown parameter, code that does not parse, a call direct control does not allow, a ref id or coordinate copied from the trajectory) and warnings.
changesFor heal: what changed and why.
usageTokens of the model calls this request made, billed to your account.

A repeated idempotency_key returns the earlier result plus "idempotent_replay": true.

Run the code

Run it in a control lease with the call to main as the block's last line; the block's result is what main returns. Pass every input: an argument to main replaces its whole default object, so merge your inputs over parameters' defaults.

json
POST /v1/machines/{machineId}/control/exec
{ "code": "<code>\nawait main({\"site\": \"https://lobste.rs\"})", "timeoutMs": 300000 }

The SDKs do this for you: lease.run(learned, inputs).

Read a result later

GET /v1/agents/learn/{learnId}

Returns the same object. A call that stopped mid-way reads as failed with error: "interrupted".

Errors

StatusBody
400Validation failed: no turns without initialCode, evaluations without initialCode, an unknown key or model.
400{ "error": "mixed_sessions" }: the turns come from more than one session.
400{ "error": "template_mismatch" }: the template does not match the first turn's message.
400{ "error": "no_steps" }: the turns have no agent steps to convert.
402{ "error": "model_refused", "message": "Out of credits. ..." }
403{ "error": "model_refused" }: your plan does not allow the model; pass another model.
404{ "error": "turn_not_found" }, { "error": "judgement_not_found" }, or learn_not_found on GET.
409{ "error": "turn_not_finished" } or { "error": "trajectory_not_recorded" }.
429More than 30 learn calls in an hour, or the model is rate limited.
502{ "error": "model_output_invalid" } or learn_failed: try again.