Start GuideBetaConcepts
OverviewLocal worker quickstartBuild a local workerSessions and claims
DocsAPI Reference

Main

  • Home
  • About
  • Pricing
  • Vault
  • Changelog
  • Docs

Features

  • Roadmaps
  • Planning
  • Standups
  • Status updates
  • Insights
  • AI assistant / MCP
  • Integrations

Solutions

  • Startups
  • Dev shops / agencies
  • Software teams
  • Internal IT & platform teams

Alternatives

  • vs Jira
  • vs Linear
  • vs Asana
  • vs Monday.com
  • vs ClickUp
  • vs Notion

Company

  • Blog
  • Security
  • Log in
  • Sign up
  • Terms of Use
  • Privacy Policy

Resources

  • Docs
  • API reference
  • CLI
  • Desktop app
  • SDK

© 2026 One Horizon. All rights reserved

FacebookInstagramThreadsXRedditTikTokYouTubeMedium


Build a local agent worker

Build a local worker when your runtime needs to execute One Horizon agent sessions on a user's machine. The loop is resource-based: register a worker, send heartbeats, poll sessions, claim one session, run it, then complete, fail, or release the claim.

If you only want to run the built-in Codex worker, use the local worker quickstart instead. The Desktop app can create and start that worker for you, and the CLI gives you the same worker loop from a terminal.

Core model

Four records make up the local worker loop:

RecordWhat it means for your worker
AgentThe app capability users can send work to.
WorkerYour local runtime instance for that agent and workspace.
SessionOne queued unit of work, often tied to a task or initiative.
ClaimThe active lease that lets your worker execute one session.

Session creation queues work. A successful claim authorizes execution.

Authentication

Local workers use OAuth user tokens. The authenticated user must own the worker and the local sessions it can see.

Do not use workspace API keys for local worker execution. API keys cannot register local workers, claim sessions, emit activities, or call worker-scoped session endpoints.

Desktop-managed workers use the signed-in Desktop session to perform setup and start the local process. That does not change the server contract: the worker still belongs to one user, one workspace, and one agent.

A local worker can run code on a user's machine. Keep local execution owner-only, and treat workspace-authored content as untrusted input.

Register the worker

Find or create a local worker for the workspace, agent, and authenticated user.

Store the returned agentId and workerId in your local config so later runs can reuse the same worker. If the server rejects the saved worker, register again instead of silently falling back to task search or direct execution.

For a graphical setup flow, mirror Desktop: ask for the repo folder, workspace mode, workflow file, worker name, and optional worker instructions. Save the returned worker ID before starting the process.

The worker record should describe:

  • Workspace and agent
  • Execution mode local
  • Owner user
  • Worker name or display label
  • Runtime support and policy limits
  • Optional worker-level custom instructions
  • Status and heartbeat timestamps

Send heartbeats

Send a heartbeat every 30 seconds while the worker runs. One Horizon marks a worker stale after 2 minutes without a heartbeat and offline after 10 minutes. When a worker goes offline, the platform expires active claims and returns those sessions to the claimable pool.

Heartbeat requests accept coarse runtime facts such as OS platform and runtime version. Do not include machine names, usernames, private paths, IP addresses, or repository names.

If the heartbeat endpoint returns 404, the worker record was deleted. Clear the saved worker ID and stop polling. Do not re-register without explicit user confirmation.

Do not emit an activity for every heartbeat. Activities should describe progress, policy decisions, plan changes, external URLs, completion, failure, or input needs.

Respond to control signals

Poll your worker record every 5 seconds while the worker is running. The worker record may carry a pending control signal set by a workspace admin or automation.

SignalRequired behavior
stopFinish the current session turn, then exit cleanly.
pauseStop dispatching new sessions. Keep any active run alive until it finishes.
resumeResume dispatching sessions after a pause.
restartStop the worker cleanly and start a fresh worker process when the launcher supports respawn. If respawn is not supported, exit after acknowledging the signal.

After acting on a signal, acknowledge it by posting to the ack-control-signal endpoint. Unacknowledged signals remain pending and will be returned on every subsequent poll.

Poll sessions

Poll sessions through the worker-scoped session endpoint for your agent and worker. The server returns only sessions this worker is allowed to claim.

For local workers, eligible sessions must belong to the authenticated owner. If the user changes accounts or workspaces locally, stop and re-register under the new context.

Claim before execution

Claim the session before treating it as executable work. The claim is the concurrency and authorization boundary.

Pass leaseSeconds in the claim request to set the lease duration. The default is 900 seconds (15 minutes). If the lease expires before your worker completes, fails, or releases the session, the platform marks the session stale and returns it to the claimable pool. Another eligible worker can claim it without any manual intervention.

When the claim succeeds, store the returned claimId with local run state. Include it in every activity, patch, completion, failure, and release call for that session. Calls without the active claimId are rejected.

If the claim returns a conflict, skip that session. Another worker claimed it first, the session was cancelled, or the worker is no longer eligible.

Keep trust boundaries clear

Build the runtime prompt or job payload from separate inputs:

  • Trusted instructions: One Horizon system policy, agent profile config, worker config, and trusted local workflow policy. Agent and worker custom instructions are trusted configuration and are limited to 2,000 characters each.
  • Untrusted context: task titles, descriptions, comments, documents, user prompts, and other workspace-authored content.

Pass trusted instructions separately from untrusted context when you call your model or runtime. Untrusted context can guide the work, but it must not change sandbox policy, credentials, allowed commands, network access, or external side effects.

Enforce worker policy

The model can request an action. The worker decides whether the action is allowed.

Keep policy checks explicit for:

  • Filesystem access
  • Shell execution
  • Network access
  • One Horizon workspace writes
  • External side effects such as commits, pushes, and pull requests

If policy blocks an action, emit a policy_decision activity with enough context for the user to understand what happened.

Emit activities

Use activities for progress that belongs in the dashboard or audit log:

  • progress
  • plan_updated
  • external_url_updated
  • awaiting_input
  • completed
  • failed
  • policy_decision

Use session metadata updates for plan text, external URLs, input requests, and error messages. Keep activity text concise and tied to one change in the run.

Finish the claim

End each claimed session in one of three ways:

  • Complete when the work succeeds.
  • Fail when the worker cannot finish and the session should be marked as errored.
  • Release when the worker stops before doing useful work or decides another eligible worker should retry.

If the process exits unexpectedly, the claim lease and worker heartbeat let One Horizon mark the session or worker stale.

Retry with backoff

When a run fails and may succeed later, retry with exponential backoff instead of reporting a final failure immediately.

Delay before each retry attempt:

min(10000 × 2^(attempt − 1), max_retry_backoff_ms)

The default backoff cap is 5 minutes. agent.max_retry_attempts in the workflow config defaults to 0. Once retries are exhausted, report a final failure so the platform can apply the configured task update for that outcome.

Workflow file configuration

If your worker uses a WORKFLOW.md file, the following fields apply when connected to One Horizon.

Supported

FieldNotes
workspace.*Repository folder, workspace mode, and working directory.
hooks.*All four hook points are supported.
agent.max_concurrent_agentsGlobal concurrency cap for this worker.
agent.max_retry_backoff_msMaximum delay between retries.
agent.max_retry_attemptsMaximum retries before reporting final failure. Default is 0.
codex.commandThe command to run. Only codex app-server is accepted.
codex.turn_timeout_msMaximum time allowed for one execution turn.
codex.stall_timeout_msInactivity timeout. Values ≤ 0 disable stall detection.
polling.interval_msHow often to poll for sessions. Default is 30 seconds.

Ignored

These fields have no effect when a worker connects to One Horizon. The platform manages them instead.

FieldReason
tracker.*One Horizon owns the tracker integration.
codex.approval_policyPolicy is delivered in the session's trusted instructions.
thread_sandboxPolicy is delivered in the session's trusted instructions.
turn_sandbox_policyPolicy is delivered in the session's trusted instructions.
agent.max_concurrent_agents_by_stateOne Horizon uses a flat concurrency cap.

Workflow files support hot reload. Changes take effect on the next poll cycle without restarting the worker.

Template variables

WORKFLOW.md content may include template variables. One Horizon resolves these at session claim time.

VariableValue
{{ issue.id }}Internal task UUID.
{{ issue.identifier }}Human-readable identifier such as T-42.
{{ issue.title }}Task title.
{{ issue.description }}Task description, if present.
{{ issue.state }}Current task state label.
{{ issue.labels }}Comma-joined label names.
{{ issue.prompt }}User prompt from the session.
{{ attempt }}Empty on the first run; retry number on subsequent attempts.

Unknown variables are left in place unchanged rather than causing an error.

Minimal worker loop

The implementation loop is the same whether your runtime starts one process or keeps a daemon running.

Mermaid
flowchart TD
  Auth["User OAuth"]
  Register["Register worker"]
  Heartbeat["Heartbeat"]
  Poll["Poll sessions"]
  Claim["Claim session"]
  Execute["Execute locally"]
  Finish{"Finish"}
  Complete["Complete"]
  Fail["Fail"]
  Release["Release"]

  Auth --> Register --> Heartbeat --> Poll --> Claim --> Execute --> Finish
  Finish --> Complete
  Finish --> Fail
  Finish --> Release
  Complete --> Heartbeat
  Fail --> Heartbeat
  Release --> Heartbeat
load local configauthenticate with user OAuthagent = find or create agent profileworker = register or reuse local worker
while running:  heartbeat(worker)  if heartbeat returns 404:    clear saved worker ID    exit
  signal = poll_worker_record(worker)  if signal == "stop":    finish current session, ack(signal), exit  if signal == "restart":    ack(signal), restart process or exit if respawn is unsupported  if signal == "pause":    ack(signal), suspend dispatch until "resume"  if signal == "resume":    ack(signal), resume dispatch
  sessions = poll_sessions(worker, status=["queued", "pending", "stale"])
  for session in sessions:    claim = claim_session(session, worker, leaseSeconds=900)    if claim failed (409):      continue
    attempt = 0    while attempt <= max_retry_attempts:      try:        payload = build_payload(          trusted_instructions=claim.session.trustedInstructions,          untrusted_context=claim.session.untrustedContext        )        result = run_local_agent(payload, local_policy)        emit_activity(session, claim.claimId, "completed")        complete(session, claim.claimId, result)        break      catch needs_user_input:        emit_activity(session, claim.claimId, "awaiting_input")        patch_session(session, claim.claimId, status="awaiting_input")        break      catch blocked_by_policy:        emit_activity(session, claim.claimId, "policy_decision")        fail(session, claim.claimId, reason)        break      catch retryable_error:        attempt += 1        if attempt > max_retry_attempts:          emit_activity(session, claim.claimId, "failed")          fail(session, claim.claimId, reason)        else:          delay = min(10000 * 2^(attempt - 1), max_retry_backoff_ms)          wait(delay)      catch error:        emit_activity(session, claim.claimId, "failed")        fail(session, claim.claimId, reason)        break

API reference

The agent work API lives under:

/api/v1/workspaces/{workspaceId}/agents

Use the API reference for exact request bodies, response bodies, operation names, generated SDK types, and status enums.

For lifecycle details, read Sessions and claims.


Related Articles

Sessions and claims

How agent sessions queue work, grant leases, report progress, and finish.

Overview

Understand agent profiles, workers, sessions, claims, and activity records.

Local worker quickstart

Create a local Codex worker from Desktop or the CLI, then run queued agent sessions.

Custom app

Create a One Horizon app for webhooks, OAuth sign-in, and API access.

  • Core model
  • Authentication
  • Register the worker
  • Send heartbeats
  • Respond to control signals
  • Poll sessions
  • Claim before execution
  • Keep trust boundaries clear
  • Enforce worker policy
  • Emit activities
  • Finish the claim
  • Retry with backoff
  • Workflow file configuration
  • Template variables
  • Minimal worker loop
  • API reference
  • Back to top