Skip to main content

Tasks, schedules, and agents API

These endpoints cover session-scoped task inspection plus managed-agent orchestration. Daemon-owned projects and project tasks now have their own separate surface. Read Projects API for that model. Sessions that are channel members can also receive daemon-created runs of kind ChannelDelivery. Those runs still execute through the normal runs API and session queue. The public conversation itself lives in the separate Channels API. Schedules, sidechains, and reviewer results can also feed public channel threads indirectly through durable channel stimuli and root-thread bindings. The schedule, task, and run themselves still remain session- and run-scoped; the channel only owns the public conversation projection.

Task endpoints

  • GET /v1/sessions/{session_id}/tasks
  • GET /v1/sessions/{session_id}/tasks/{task_id}
  • GET /v1/sessions/{session_id}/tasks/{task_id}/output
  • POST /v1/sessions/{session_id}/tasks/{task_id}/stop

List and inspect tasks

GET /v1/sessions/{session_id}/tasks supports:
  • status: filter by pending, in_progress, blocked, completed, failed, or cancelled
Task detail returns one TaskRecord with fields such as:
  • id
  • title
  • description
  • status
  • owner_agent_id
  • blocked_by
  • blocks
  • output
  • metadata
  • created_at_ms
  • updated_at_ms

Read task output

GET /v1/sessions/{session_id}/tasks/{task_id}/output supports:
  • wait: wait for output or state change before returning
  • timeout_ms: wait duration when wait=true
  • tail_bytes: excerpt size, defaulting to a small tail view
  • full: include full captured output text when available
The response is a TaskOutputView:
  • retrieval_status
  • task
  • output_file_path
  • output_excerpt
  • output_text
  • output_truncated
  • output_size_bytes
Current retrieval_status values include:
  • success
  • not_ready
  • timeout
retrieval_status: "success" means the daemon read the task output view. It does not mean the task succeeded. For daemon-managed shell tasks, treat command success as task.status: "completed" plus task.metadata.exit_code: 0. If task.status is failed or cancelled, clients and agents must not report the shell task as successful even when output retrieval succeeded. Example:
curl "http://127.0.0.1:4000/v1/sessions/demo/tasks/task-1/output?wait=true&timeout_ms=2000&tail_bytes=8192"
After daemon restart, an active daemon-managed shell task returns as task.status: "failed" with task.metadata.terminal_reason: "daemon_restarted" and task.metadata.recovered_on_boot: true; the output endpoint still returns partial captured output when available.

Stop a task

POST /v1/sessions/{session_id}/tasks/{task_id}/stop accepts:
{
  "reason": "operator requested stop"
}

Schedule endpoints

  • GET /v1/schedules
  • POST /v1/schedules
  • GET /v1/schedules/{schedule_id}
  • POST /v1/schedules/{schedule_id}/cancel
  • POST /v1/schedules/{schedule_id}/pause
  • POST /v1/schedules/{schedule_id}/resume
  • POST /v1/schedules/{schedule_id}/trigger
GET /v1/schedules supports:
  • session_id

Create a schedule

POST /v1/schedules accepts a ScheduleCreateRequest. Required fields:
  • name
  • target_session_id
  • cadence
  • exactly one of:
    • request
    • observation_materialization
Optional fields:
  • target_agent_id
  • owner_session_id
  • owner_agent_id
  • created_by_run_id
  • max_executions
  • overlap_policy
  • misfire_policy
Important note:
  • request is a full SubmitInputRequest
  • when the nested schedule request uses only input_items, include "content": "" to match the current request shape

Cadence variants

once:
{
  "type": "once",
  "fire_at_ms": 1760000000000
}
interval:
{
  "type": "interval",
  "every_seconds": 300
}
cron:
{
  "type": "cron",
  "expression": "0 */6 * * * *",
  "timezone": "UTC"
}
Important validation rules:
  • interval cadence must be at least 5 seconds
  • cron timezone defaults to UTC
  • max_executions, when set, must be greater than zero
  • when observation_materialization is used, its target_session_id must match the schedule target_session_id

Schedule policy fields

overlap_policy:
  • skip
  • queue_one
  • parallel
misfire_policy:
  • { "type": "coalesce_once" }
  • { "type": "skip_missed" }

Schedule creation example

{
  "name": "nightly-review",
  "target_session_id": "ops-review",
  "cadence": {
    "type": "cron",
    "expression": "0 0 2 * * *",
    "timezone": "Europe/Paris"
  },
  "overlap_policy": "skip",
  "misfire_policy": {
    "type": "coalesce_once"
  },
  "request": {
    "content": "Review the latest state and write a concise overnight summary.",
    "generation": {
      "model": "gpt-5.4",
      "tool_choice": {
        "type": "auto"
      },
      "allow_parallel_tool_calls": true,
      "response_format": {
        "type": "text"
      }
    }
  }
}
The response is a ScheduleView. Important fields include:
  • schedule_id
  • name
  • target_session_id
  • status
  • cadence
  • overlap_policy
  • misfire_policy
  • max_executions
  • next_fire_at_ms
  • queued_fire_at_ms
  • in_flight_fire_at_ms
  • in_flight_run_id
  • last_fire_at_ms
  • last_dispatched_run_id
  • last_error
  • execution_count
  • consecutive_failures
  • request
ScheduleView.misfire_policy is returned with the same tagged-object shape. Pause, resume, cancel, and manual trigger all return:
{
  "schedule": {
    "...": "ScheduleView"
  }
}

Agent endpoints

  • GET /v1/agents
  • GET /v1/agents/{agent_id}
  • POST /v1/agents/{agent_id}/sidechains
  • GET /v1/agents/{agent_id}/mailbox
  • POST /v1/mailboxes
GET /v1/agents and GET /v1/agents/{agent_id} return ManagedAgentSnapshot records. These include the static agent record plus live state such as pending approvals, pending questions, the last assistant message, and the last runtime error. Public channel participation does not introduce a second agent runtime. A member session still uses the normal agent snapshot, mailbox, and run queue while the daemon separately keeps the public channel log and turn leases.

Spawn a sidechain

POST /v1/agents/{agent_id}/sidechains accepts SpawnSidechainRequest. Top-level fields:
  • session_id
  • thread_id
  • route_policy
  • provider
  • permission_mode
  • retention
  • nickname
  • spawn_request_id
  • fork_context
  • generation
  • tool_surface
  • capability_scope
  • credential_scope
  • subtask
The minimum required fork_context shape is:
  • parent_assistant_message
  • inherited_tool_call_ids
  • system_prompt
Optional fork_context fields:
  • team_name
  • isolation
  • prompt_merge_mode
  • provider
  • generation
  • tool_surface
  • worktree_path
Example:
{
  "nickname": "research-child",
  "retention": "retain",
  "route_policy": {
    "provider": "openai",
    "generation": {
      "model": "gpt-5.4",
      "tool_choice": {
        "type": "auto"
      },
      "allow_parallel_tool_calls": true,
      "response_format": {
        "type": "text"
      }
    }
  },
  "fork_context": {
    "parent_assistant_message": "Split this into a focused research task.",
    "inherited_tool_call_ids": ["call_1", "call_2"],
    "system_prompt": "You are a focused research sub-agent.",
    "prompt_merge_mode": "append"
  },
  "subtask": {
    "name": "Research stale-while-revalidate",
    "description": "Collect the most important implementation patterns and tradeoffs.",
    "content": "Produce a concise technical note with concrete references."
  }
}
Route normalization rules:
  • route_policy is the preferred modern form
  • compatibility provider plus generation may still be supplied
  • conflicting combinations are rejected with 400 Bad Request
Persona, capability, and credential inheritance:
  • child sessions inherit the parent’s bound persona snapshot at spawn time
  • child sessions also inherit the parent’s persisted capability scope
  • child sessions also inherit the parent’s persisted credential scope
  • child sessions inherit the parent’s effective permission mode unless permission_mode requests a narrower mode
  • explicit permission_mode: "default" is pinned as a child session override instead of falling through to the daemon global mode
  • sidechain spawn rejects permission_mode values that would widen the parent, for example bypassPermissions from a default parent
  • parent session-scoped permission updates are copied to the child session at spawn time
  • when the spawn request omits credential_scope, delegated children keep route access but lose connector credentials and credentialed MCP access by default
  • child capability_scope, credential_scope, and tool_surface can narrow the inherited surface further but cannot widen it

Mailbox delivery

POST /v1/mailboxes posts one typed JSON envelope and returns 202 Accepted. Example:
{
  "from_agent_id": "agent-1",
  "to_agent_id": "agent-2",
  "subject": "research-result",
  "payload": {
    "summary": "Three candidate approaches",
    "winner": "option-b"
  }
}
GET /v1/agents/{agent_id}/mailbox returns the currently queued mailbox messages without draining them. Agent-side mailbox processing drains messages through the runtime control path; this HTTP inspection endpoint is safe to repeat during debugging.
  • it returns the currently queued mailbox messages
  • repeat reads can return the same messages until an agent/runtime path consumes them

Evidence Note

  • Code verified: crates/kheish-daemon/src/api/handlers.rs, crates/kheish-daemon/src/main.rs, crates/kheish-daemon/src/control_tools.rs, crates/kheish-daemon/src/control_tools/tasks.rs, crates/kheish-daemon/src/services/task.rs, crates/kheish-daemon/src/state/subagent_spawn.rs, crates/kheish-daemon/src/state/session_state.rs, crates/kheish-types/src/model.rs, crates/kheish-agent/src/supervisor.rs.
  • CLI/API verified: task output semantics checked against TaskOutputView; mailbox endpoint checked against HTTP handler and supervisor peek behavior.
  • Daemon live tested for this note: no; deterministic service tests cover failed-task output retrieval.
  • Provider-specific tested for this note: no; task output retrieval is daemon-local.