EtherDocs
Integrate/Harness API

Harness API

External harnesses integrate through /api/harness/* on api.tryether.ai. This is separate from legacy internal os-api routes.

OpenAPI source

ether-core/docs/openapi/harness-v1.yaml

Mental model

Claim gives you everything needed to run one attempt:

  • Prompt, definition of done, repo/branch scope
  • Context document IDs (MD corpus)
  • Callback URLs for heartbeat, steps, interrupt, result
  • WebSocket topic for live UI

Return through those callbacks so Ether can update the task, memory, and user-facing delivery.

Routes

MethodPathPurpose
GET/api/harness/tasks/{task_id}/executionPreview bundle without claiming
POST/api/harness/tasks/{task_id}/claimAcquire lease → ExecutionBundle
POST/api/harness/tasks/{task_id}/heartbeatExtend lease
POST/api/harness/tasks/{task_id}/stepsUser-visible progress
POST/api/harness/tasks/{task_id}/interruptPause task; ask user a question
POST/api/harness/tasks/{task_id}/resultFinal outcome
GET/api/harness/context?task_id=&slices=Context slice metadata
GET/api/harness/context/doc/{doc_id}?task_id=Markdown document body
GET/api/harness/toolsTool manifest
GET/api/harness/skills?domain=Skill manifest

Claim request

json
{
  "app_id": "my-harness"
}

Response includes run_id, lease_expires_at, and bundle with callbacks:

json
{
  "callbacks": {
    "report_step_url": "https://api.tryether.ai/api/harness/tasks/task_abc/steps",
    "heartbeat_url": "…/heartbeat",
    "interrupt_url": "…/interrupt",
    "result_url": "…/result",
    "websocket_topic": "tasks/task_abc"
  }
}
Production rule

Use these URLs from the bundle — do not hardcode paths in production.

Execution result

json
{
  "run_id": "run_…",
  "status": "submitted",
  "agent_status": "Opened PR #42",
  "artifact": {
    "pr": {
      "number": 42,
      "branch": "ether/fix",
      "url": "https://github.com/org/repo/pull/42"
    },
    "agent_status": "Opened PR #42"
  },
  "working_summary": "Optional notes for memory compaction"
}

Status values

StatusEther behavior
submittedTask → Completed; task.delivered published
blockedTask → Paused; question surfaced to user
no_changesProgress updated; harness should explain via no_changes_reason

During execution

CallWhen
heartbeatPeriodically while long runs execute
stepsUser-facing narration (“Pulling context…”, “Opening PR…”)
interruptNeed user input mid-run (alternative to blocked result)

Steps emit os.harness.step on the task WebSocket topic.

Context documents

DocContent
profile.mdOwner preferences
project.mdStack, conventions
repo.mdRepository notes
task/{task_id}.mdActive goal
working/{run_id}.mdWritten back when you send working_summary

Conflicts

409 on claim — another harness holds an active lease with a different app_id. Same app can reclaim its run.

Division of responsibility

EtherHarness
Task interpretation & deliveryModel + tool loop
Context corpus & compactionGit checkout, file edits
Lease & timelinePR creation, tests
Judge + deliverable (on submit)Escalation policy