Headless Control API

The Control API is the same control plane used by Desktop. It is also available to servers, scripts, and lab automation. Prefer scientific-agent run for a single configuration; use the API when you need creation, pause/resume, queries, uploads, or live events.

Start the service

In the first terminal:

scientific-agent \
  --data-dir ./research-data \
  serve --host 127.0.0.1 --port 8000

Check it from a second terminal:

curl -sS http://127.0.0.1:8000/api/v1/health | jq

Interactive API documentation is at http://127.0.0.1:8000/api/v1/docs. serve is an unauthenticated local development mode. Keep it bound to 127.0.0.1; do not expose it directly to a LAN or the internet. Desktop starts its own token-protected service, which should not be mixed with this one.

One complete API run

Save the following as run.json:

{
  "title": "API toy run",
  "goal": "Reach a score of at least 0.9",
  "acceptance": {
    "metric": "score",
    "operator": ">=",
    "value": 0.9
  },
  "budget": {
    "max_actions": 5,
    "max_experiments": 3,
    "max_failures": 2
  },
  "capabilities": ["toy.evaluate"],
  "research_questions": ["Can the deterministic candidate pass?"],
  "reasoner": {"type": "deterministic"}
}

Preview the contract without creating a run:

curl -sS -X POST \
  -H 'Content-Type: application/json' \
  --data @run.json \
  http://127.0.0.1:8000/api/v1/runs/preview | jq

Create it and retain the run_id:

curl -sS -X POST \
  -H 'Content-Type: application/json' \
  --data @run.json \
  http://127.0.0.1:8000/api/v1/runs | tee created.json | jq

RUN_ID=$(jq -r '.summary.run_id' created.json)
curl -sS -X POST "http://127.0.0.1:8000/api/v1/runs/$RUN_ID/start" | jq

Read state and timeline:

curl -sS "http://127.0.0.1:8000/api/v1/runs/$RUN_ID/state" | jq
curl -sS "http://127.0.0.1:8000/api/v1/runs/$RUN_ID/events?cursor=0&limit=100" | jq

Live updates

The Server-Sent Events stream works well for progress panels and supervisors:

curl -N "http://127.0.0.1:8000/api/v1/runs/$RUN_ID/stream?after=0"

Every update has an increasing cursor. On reconnect, put the last cursor in after, or send it as Last-Event-ID, to avoid replaying from the beginning.

Lifecycle operations

curl -sS -X POST "http://127.0.0.1:8000/api/v1/runs/$RUN_ID/pause" | jq
curl -sS -X POST "http://127.0.0.1:8000/api/v1/runs/$RUN_ID/resume" | jq
curl -sS -X POST "http://127.0.0.1:8000/api/v1/runs/$RUN_ID/recover" | jq
curl -sS -X POST "http://127.0.0.1:8000/api/v1/runs/$RUN_ID/cancel" | jq

recover applies only to an interrupted run with a recovery point. For cancel and recover, add a unique X-Idempotency-Key so a retried script does not apply a command twice.

Ask and intervene

Ask answers from the recorded study and returns references to events, metrics, jobs, or artifacts:

curl -sS -X POST \
  -H 'Content-Type: application/json' \
  --data '{"message":"Why is the run not complete?"}' \
  "http://127.0.0.1:8000/api/v1/runs/$RUN_ID/ask" | jq

Add a research note:

curl -sS -X POST \
  -H 'Content-Type: application/json' \
  --data '{
    "directive_type":"ADD_RESEARCH_NOTE",
    "reason":"Record an operator observation",
    "note":"Repeat the evaluation if score variance exceeds 0.02"
  }' \
  "http://127.0.0.1:8000/api/v1/runs/$RUN_ID/intervene" | jq

Other directive_type values are PAUSE_RUN, RESUME_RUN, and UPDATE_BUDGET. Include only fields to change in the budget object; max_experiments: null removes the experiment limit. To auto-resume from BUDGET_EXHAUSTED, at least one existing limit must actually increase or be removed. Every intervention is audited.

Query research results

Useful read endpoints include:

Content Path suffix
Overview /overview
Analyses /analyses
Evidence, claims, hypotheses /evidence, /claims, /hypotheses
Verifications and reports /verifications, /reports
Metrics, budget, resources /metrics, /budget, /resources
Jobs and model calls /jobs, /model-calls
Artifacts and lineage /artifacts, /lineage, /research-flow
Ask/intervention history /interactions

Append the suffix to /api/v1/runs/$RUN_ID. Paginated lists accept cursor and limit, with at most 250 items per page.

To download an artifact, obtain its artifact_id from /artifacts first:

curl -sS \
  "http://127.0.0.1:8000/api/v1/runs/$RUN_ID/artifacts/$ARTIFACT_ID/content" \
  -o result.bin

Upload an input artifact as Base64:

{
  "name": "measurements.csv",
  "artifact_type": "OTHER",
  "content_base64": "YSxiCjEsMgo="
}

POST it to /api/v1/runs/$RUN_ID/artifacts. Upload run inputs before starting the run.

Scientific commands

Automation can explicitly request analyses, reports, hypotheses, and verification rather than disguising commands as chat:

The report example below assumes a non-frozen run that can still accept scientific commands, a contract that authorized scientific.report.generate, and an existing visible anchor artifact. The minimal toy contract above did not authorize reporting, so reusing it would correctly return a missing-capability error.

curl -sS -X POST \
  -H 'Content-Type: application/json' \
  -H 'X-Idempotency-Key: report-request-001' \
  --data '{
    "command_type":"RequestReport",
    "report_title":"Final assessment",
    "include_figures":true,
    "reason":"Prepare the review package"
  }' \
  "http://127.0.0.1:8000/api/v1/runs/$REPORT_RUN_ID/scientific-commands" | jq

Command types are RequestAnalysis, RequestReport, SubmitHypothesis, CreateVerificationPlan, ApproveVerificationPlan, RequestVerification, and UpdateHypothesisStatus. Each requires different references and fields; use the ScientificCommandRequest schema in /api/v1/docs as the source of truth.

Capabilities, plugins, storage, and exports

curl -sS http://127.0.0.1:8000/api/v1/capabilities | jq
curl -sS http://127.0.0.1:8000/api/v1/plugins | jq
curl -sS http://127.0.0.1:8000/api/v1/storage/info | jq
curl -sS -X POST http://127.0.0.1:8000/api/v1/storage/backups | jq

Manage plugins with POST /plugins/{plugin_id}/install, /enable, and /disable. A custom ZIP goes to POST /plugins/custom/install with filename and content_base64. Installation validates and stores the package; enabling it is what imports code.

Create a portable scientific export from a successful run:

curl -sS -X POST \
  -H 'Content-Type: application/json' \
  --data "{\"run_id\":\"$RUN_ID\",\"name\":\"review-package\"}" \
  http://127.0.0.1:8000/api/v1/exports | jq

Automation checklist

  • Call /health before creating a run.
  • Preview the contract and check valid and warnings first.
  • Persist the run_id, command idempotency keys, and SSE cursor.
  • Parse JSON state; do not infer success from terminal prose.
  • Never edit event files or artifact directories behind the API.
  • A multi-user or remote deployment needs a separate reverse proxy, authentication, and TLS; built-in serve is not a public service.

results matching ""

    No results matching ""