Session host
harness.host.http
Section titled “harness.host.http”The session host’s HTTP server: the API every frontend drives conversations and follows episodes through, over a runs directory, and the app package’s pages when it is installed.
Standard library only (http.server). Every API call needs the session key: the
token query parameter of the URL harness app prints (the page then keeps it in a
cookie), or the X-Session-API-Key header. Records are followed with server-sent
events: a client that reconnects sends Last-Event-ID (the last seq it has) and gets
what came after it. Every record they send carries view, its items (transcript.py).
GET /api/runs the episodes, newest first GET /api/meta?run=R an episode’s manifest, summary, ending and usage GET /api/events?run=R&after=N its trace records after seq N GET /api/lanes?run=R its timeline’s lanes, spans and markers (core/lanes.py Lanes.snapshot), what every web view draws GET /api/stream?run=R the same, then new ones as they are written GET /api/file?run=R&name=F one of its files (videos, logs), or a range of it
The page at / (the app’s), the runs page at /dev, and at
/overlay?run=R the timeline overlay’s (the app package’s overlay: one run’s timeline, or
without run the newest session that runs). Another package’s pages are at the paths its host
module declares (pages(), harness/extensions.py), their files at /static/<package>/<file>;
its API (api) at /api/<package>/<name>, GET and POST alike, with the session key as every
other route.
Conversations the server runs itself (runtime/conversation.py, interactive), written under <runs>/ui/; a POST takes a JSON body and the session key:
GET /api/options experiments, model files and the deploy to start from GET /api/libraries what the agent learned: the libraries under $HARNESS_HOME GET /api/library?name=N | run=R a library’s entries, or a run’s memory beside its library’s (runtime/task/context.py rows: one format, columns and cells) POST /api/conversations start one: experiment, task, seed, model, endpoint, instruction, confirmation, sets, deploy, title, frontend, workspace (a kind: the app’s server takes one from its pool for the session); {run, session} POST /api/conversations/message {run, text, files}: a message for the model’s next call (files: paths on this computer it attaches) POST /api/conversations/pause {run}: stop at the next execution step POST /api/conversations/resume {run} POST /api/conversations/model {run, model, reasoning_effort}: the main model (another id of its provider) and its effort (none, low, medium, high; “” the provider’s default) from its next call on POST /api/conversations/confirm {run, accept, reason, remember}: the verdict on a waiting program, or on a step waiting for physical input (remember: once, app, session, always) POST /api/conversations/close {run}: end the episode
A conversation’s programs may start sub-agents (runtime/task/delegation.py); this server gives them no workspaces (it has no pool: delegate says so), the app’s does (app.py).
A run is the path of an episode under the runs directory or, for a conversation the server runs, also its session’s id (“<session>/<agent>” for an agent of it). The sessions it runs, each a tree of agents (host/sessions.py):
GET /api/sessions the sessions, newest first GET /api/sessions/show?id=S&lines=N one session, its agents and the last lines of each POST /api/sessions/stop {id, agent}: end its agents (or that one) POST /api/sessions/approve {id, agent, accept, reason}: the verdict on what waits
statics
Section titled “statics”def statics() -> dict{package: its ui/static/ folder} of the packages this process uses that have one.
static
Section titled “static”def static() -> Path | NoneThe folder of the page at / and of the files at /static/<file>: the app package’s ui/static/; without it, that of the first package whose host module gives a page at /, else of the first package that has pages. None when no package this process uses has them.
def pages() -> dict{path: (folder, file)}: the pages this server has. The legacy ones (PAGES) are files
of static()’s folder; a package’s host module adds its own (pages, harness/extensions.py),
files of its ui/static/. The page at / stays the app’s; “/”: None is the server’s own
(Server.page).
package_api
Section titled “package_api”def package_api(path: str)The handler a package’s host module gives for /api/<package>/<name> (its api,
harness/extensions.py), or None.
bench_kind
Section titled “bench_kind”def bench_kind(bench: str, worker: dict | None=None) -> str | NoneThe workspace kind an experiment’s worker serves (None: one no pool starts).
lanes_of
Section titled “lanes_of”def lanes_of(directory: Path) -> dictThe lanes of the run in directory (core/lanes.py: the one computation of them), as
a snapshot every web view draws; computed again only when its trace changed.
Server
Section titled “Server”class Server(OwnPort, ThreadingHTTPServer) __init__(address, root: Path, token: str | None=None, deploy: str | None=None)The GUI’s HTTP server over the runs directory root, with the session key
token (a new random one by default).
def url() -> strThe address to open, with the session key.
def info() -> dictWhat the page is told about the server, the statuses of a conversation and the words of an approval.
def touch() -> NoneA client asked for something (the session host counts this as activity).
model_changed
Section titled “model_changed”def model_changed(run: str, changed: dict) -> NoneA conversation’s model or reasoning effort changed (POST /api/conversations/model).
session_directory
Section titled “session_directory”def session_directory(request: dict, config, seed: int) -> tuple[Path, str | None]Where a conversation started here is written, and its run (None: its session’s id).
directory
Section titled “directory”def directory(run: str) -> PathThe episode directory of run: a conversation’s this server runs, else an episode
under the root (ValueError when there is none).
sub_agents
Section titled “sub_agents”def sub_agents(kind: str | None)The sub-agents of a session started here whose workspace is of kind (None: a
bench no workspace pool serves).
start_conversation
Section titled “start_conversation”def start_conversation(request: dict) -> strStart an interactive conversation (see the module docstring) and return its run.
workspace
Section titled “workspace”def workspace(request: dict, experiment)The workspace a conversation asked for ({endpoint, python, release}), or None. This server keeps none: the app’s server takes one from its pool.
Handler
Section titled “Handler”class Handler(BaseHTTPRequestHandler)log_message
Section titled “log_message”def log_message(format, *args)send_response
Section titled “send_response”def send_response(code, message=None)do_GET
Section titled “do_GET”def do_GET()do_POST
Section titled “do_POST”def do_POST()get_options
Section titled “get_options”def get_options(query)post_conversations
Section titled “post_conversations”def post_conversations(body)post_conversations_message
Section titled “post_conversations_message”def post_conversations_message(body)post_conversations_steer
Section titled “post_conversations_steer”def post_conversations_steer(body)post_conversations_cancel
Section titled “post_conversations_cancel”def post_conversations_cancel(body)post_conversations_config
Section titled “post_conversations_config”def post_conversations_config(body)post_conversations_answer
Section titled “post_conversations_answer”def post_conversations_answer(body)post_conversations_diff
Section titled “post_conversations_diff”def post_conversations_diff(body)post_conversations_merge
Section titled “post_conversations_merge”def post_conversations_merge(body)post_conversations_discard
Section titled “post_conversations_discard”def post_conversations_discard(body)post_conversations_terminal
Section titled “post_conversations_terminal”def post_conversations_terminal(body)post_conversations_terminal_resize
Section titled “post_conversations_terminal_resize”def post_conversations_terminal_resize(body)post_conversations_pause
Section titled “post_conversations_pause”def post_conversations_pause(body)post_conversations_resume
Section titled “post_conversations_resume”def post_conversations_resume(body)post_conversations_confirm
Section titled “post_conversations_confirm”def post_conversations_confirm(body)post_conversations_model
Section titled “post_conversations_model”def post_conversations_model(body)post_conversations_close
Section titled “post_conversations_close”def post_conversations_close(body)get_sessions
Section titled “get_sessions”def get_sessions(query)get_sessions_show
Section titled “get_sessions_show”def get_sessions_show(query)post_sessions_stop
Section titled “post_sessions_stop”def post_sessions_stop(body)post_sessions_approve
Section titled “post_sessions_approve”def post_sessions_approve(body)get_info
Section titled “get_info”def get_info(query)get_libraries
Section titled “get_libraries”def get_libraries(query)get_library
Section titled “get_library”def get_library(query)get_runs
Section titled “get_runs”def get_runs(query)get_meta
Section titled “get_meta”def get_meta(query)get_events
Section titled “get_events”def get_events(query)get_lanes
Section titled “get_lanes”def get_lanes(query)get_file
Section titled “get_file”def get_file(query)get_stream
Section titled “get_stream”def get_stream(query)FilesHandler
Section titled “FilesHandler”class FilesHandler(SimpleHTTPRequestHandler)The files under a directory, read-only and without a key (serve_files), with ranges.
send_head
Section titled “send_head”def send_head()log_message
Section titled “log_message”def log_message(*args)serve_files
Section titled “serve_files”def serve_files(root: Path, host: str='127.0.0.1', port: int=8000) -> NoneServe the files under root until interrupted (harness view); print where.
harness.host.client
Section titled “harness.host.client”A conversation run elsewhere, driven from here (after OpenHands’ RemoteConversation).
RemoteConversation talks to the session host (host/http.py) with the calls of a
local TaskRun (runtime/task/run.py): send_message, pause, resume, confirm,
close, and the trace records as they are written. So a script or a terminal on a
laptop drives an episode that runs where the worker and the models are, over an ssh
tunnel to the server’s port. It is also how every frontend drives a session of the
session host (host/): run is then the session’s id, or “<session>/<agent>”.
RemoteError
Section titled “RemoteError”class RemoteError(RuntimeError)The server refused a request; the message is its reason.
HostGone
Section titled “HostGone”class HostGone(RemoteError)The server stopped answering: no connection for GONE_SECONDS in a row.
RemoteConversation
Section titled “RemoteConversation”class RemoteConversation __init__(url: str, token: str, run: str, timeout: float=60.0, session: str | None=None)The conversation run on the GUI server at url (http://host:port), with the
session key token. status is the last status its records reported.
def start(url: str, token: str, **request) -> 'RemoteConversation'Start a conversation on the server: experiment, task, seed, model, endpoint, instruction, confirmation, sets (host/http.py start_conversation).
describe
Section titled “describe”def describe() -> dictThe session this conversation belongs to, as the server lists it (its status,
what it waits for, its agents); refreshes status.
memory
Section titled “memory”def memory() -> list[dict]What it remembers: this run’s entries (what it saw and where) and its library’s, in one format (runtime/task/context.py rows), as every frontend shows them.
send_message
Section titled “send_message”def send_message(text: str, files=()) -> NoneA message from the user: for the model’s next call while a turn runs, else it starts
the conversation’s next turn; files: paths it attaches.
def steer(text: str, files=()) -> NoneRedirect an active turn of an external session that supports steering.
cancel
Section titled “cancel”def cancel() -> NoneEnd the turn that runs; the conversation waits for the next message.
set_config
Section titled “set_config”def set_config(identifier: str, value) -> dictSelect an advertised session configuration value.
answer
Section titled “answer”def answer(request_id: str, content: dict | None) -> NoneAnswer the current external form, or decline it with None.
def diff() -> dictReview the working changes in a Git-backed external session.
def merge() -> dictFast-forward the original branch to a closed session’s committed work.
discard
Section titled “discard”def discard() -> dictRemove a closed session’s managed worktree.
terminal_input
Section titled “terminal_input”def terminal_input(text: str) -> NoneSend terminal input to a session started with an interactive PTY.
terminal_resize
Section titled “terminal_resize”def terminal_resize(columns: int, rows: int) -> NoneMatch a hosted PTY to the attached terminal’s character dimensions.
handoff
Section titled “handoff”def handoff(agent: str, context: str, *, folder=None, isolation=None)Start another agent with the context chosen by the user.
enable_agents
Section titled “enable_agents”def enable_agents() -> NoneSwitch sub-agents on for this conversation (off unless session.agents is on): their calls load before its next program.
def pause() -> NonePause at the next execution step.
resume
Section titled “resume”def resume() -> NoneGo on after a pause.
confirm
Section titled “confirm”def confirm(accept: bool, reason: str='', remember: str | None=None, *, request_id: str | None=None) -> NoneApprove or reject the program waiting for confirmation, or the step waiting for physical input (remember: once, app, session, always).
set_model
Section titled “set_model”def set_model(model: str | None=None, reasoning_effort: str | None=None) -> dictAnother main model (an id of its provider) or reasoning effort from its next call on.
def close() -> NoneEnd the conversation.
records
Section titled “records”def records(after: int=-1, stop=None)The trace records after seq after, then each new one as it is written, until
the conversation’s last record (a generator; a dropped connection is resumed; a
turn’s end is its turn_result record, after which the conversation waits). With
stop (a threading.Event), it ends once the event is set, within the server’s
keep-alive interval. When the server stops answering, it tries again, waiting longer
each time, and raises HostGone after GONE_SECONDS of failures without records or keep-alives.
harness.core.episodes
Section titled “harness.core.episodes”The episodes under a runs directory, as the GUI lists and reads them.
An episode is a directory with an events.jsonl (runtime/conversation.py); its
manifest.json and summary.json say what ran and how it ended. An episode without a
summary, or whose summary is open (a conversation between two turns), is still being
written (or was cut off).
new_name
Section titled “new_name”def new_name(suffix: str='') -> strA new run’s directory name: “20261102-140530-3fa9c1”, then “-<suffix>” when given.
summary
Section titled “summary”def summary(directory) -> dictAn episode’s summary.json, {} when it has none or it cannot be read.
def usage(source) -> dict | None{model_calls, cost_usd} of a summary or of a directory’s (None when it has none).
ending
Section titled “ending”def ending(found: dict | None) -> str | NoneHow an episode ended (its summary’s status, as ENDINGS names it); None without one.
episode_dir
Section titled “episode_dir”def episode_dir(root: Path, run: str) -> PathThe episode directory run (a path relative to root); ValueError when it leaves
the root or has no trace.
describe
Section titled “describe”def describe(root: Path, directory: Path) -> dictOne episode as the list shows it: where it is, its task, seed, status and ending, when its trace last changed, and whether it is still being written.
list_episodes
Section titled “list_episodes”def list_episodes(root: Path, limit: int=500) -> list[dict]The episodes under root, the most recently changed first (at most limit).
read_records
Section titled “read_records”def read_records(directory: Path, after: int=-1) -> list[dict]The trace records of an episode with seq above after (a line still being
written is left for later).
Follow
Section titled “Follow”class Follow __init__(directory: Path, after: int=-1)New records of a trace as they are written: next() returns the records added since the previous call (complete lines only).
def next() -> list[dict]def ended() -> boolWhether the episode has its last summary (nothing more will be written).
harness.transcript
Section titled “harness.transcript”A trace as people read it, for every frontend: items() gives a record’s items {kind, role,
text, keys}, which the host sends as its view; presentation() is the only reading of a reply.
presentation
Section titled “presentation”def presentation(record: dict) -> dictA model_reply as {kind, thought, body}; one without presentation (older) read from its text.
answer_of
Section titled “answer_of”def answer_of(record: dict) -> str | NoneThe agent’s answer when record is the reply that ends its turn (harness run -p), else None.
physical
Section titled “physical”def physical(request: dict) -> tuple[str, str]A physical-input request’s doing.* key and app: its window title’s end, else its process.
def items(record: dict, names: dict, timeline: bool=False) -> list[dict]A record’s items; names (filled in here) names the spans and entries started so far,
timeline adds the model calls and how the episode ended.
approval_options
Section titled “approval_options”def approval_options(item: dict) -> list[tuple[str | None, str]]The approval scopes and labels offered for a transcript request.
item_lines
Section titled “item_lines”def item_lines(item: dict) -> list[tuple[str, str]]The plain-text lines of one transcript item, each with its role.
record_lines
Section titled “record_lines”def record_lines(record, names, timeline=False) -> list[tuple[str, str]]The lines of one record, each with its role. Names and timeline are passed to items().
TextTranscript
Section titled “TextTranscript”class TextTranscript __init__(timeline=False)Lines for the records of one conversation, in order, each after the path of its
lane (blank for the conversation’s own: the user’s messages, its status); with
timeline, the lines a finished episode’s record shows too (record_lines).
def parts(record) -> list[tuple[str, str, str]]The lines of record as (kind, lane path, text).
def lines(record) -> list[str]harness.terminal
Section titled “harness.terminal”A conversation’s records as lines for a terminal (harness run, harness replay, a session’s last lines), each after the path of its lane on the timeline (core/lanes.py: model, e0, e0/e2, env); and the commands the user gives a conversation, one table for the terminal chat (tui/) and harness run rpc.
Command
Section titled “Command”class Command(NamedTuple)A word the user gives a conversation: its method, called with fixed and the values
for takes (a terminal gives the words after it to the first); words, note: its help.
One without a method is the terminal chat’s own (tui/chat.py do_<name>).
command
Section titled “command”def command(conversation, name: str, values: dict | None=None) -> NoneDo COMMANDS[name] on conversation with the user’s values of its takes (empty ones
left out); KeyError for a name that is none.
harness.extensions
Section titled “harness.extensions”What packages add to the session host and its frontends. A package names a module of its own in package.toml ([provides] host = “module”); the core calls what that module defines by name and imports no package itself. A host module may define:
Workspaces(directory, settings) a WorkspaceProvider (harness.sdk). All installed providers participate: kinds, selection, binding, configuration, provisioning and release belong to the package. Duplicate kind names are rejected; the core never falls back to a built-in backend.note(values) -> str a line about this computer over the terminal chat's /settingswaiting(owner) -> str | None what an agent of a session waits for (the input lock)recording(directory) -> dict a run's screen recording ({"path", "start", "stop", "area"}), or Noneareas(text) -> tuple a screen area "x,y,width,height" as numbersChromes(directory, limit) the headless Chromes a suite's background browsers shareoverlay the timeline overlay (the app package's: a window on top of the screen; launch, for_session, for_runs, close, Options)focus() -> bool bring the package's window to the front (the app's, taptab's); False when it is not open (focus() tries each in turn)session_adapter(name, directory, *, cwd, isolation, model, effort) a host-managed external conversation, or None for a name this package does not provide. Its trace lives in directory.model_backend(config) the transport of a model role (runtime/decision/model.py MODEL_KEYS) whose `backend` this package provides, or None for another backend: called like a Chat Completions request (url, headers, payload, timeout), it answers in that shape. `streams_text` true: it takes on_text; `cancellable` true: it takes cancellationpages the package's pages: {"/taptab": "taptab.html"}, files of its ui/static/ served at those paths (host/http.py); its other files are at /static/<package>/<file>api the package's API: {"inbox": handler}, served at /api/<package>/inbox with the session key; handler(server, data) -> JSON, or (body, content type, headers) as it is (data: a GET's query, a POST's JSON body; a ValueError or KeyError answers 400 with its text)mcp_servers(app) -> dict MCP servers for the conversations of the session host's assistant (the app's connectors), {name: client config}; close_app(app) when the assistant closesexport_video(directory, ...) a run as a video, used by a frontend commandprofiles {name: path}: conversation profiles a frontend starts the assistant's conversations with (POST /api/app/conversations {profile}); each file names skills only its conversations have and prompt text (host/assistant.py PROFILE_KEYS)background(server) a service with start(), close(), and keep_alive(). Optionally registered(run, conversation) is called after session registration, before execution.Standard library only.
def hosts() -> list(package, host module) of the packages this process uses that give one, in their order (paths.packages).
host_problems
Section titled “host_problems”def host_problems() -> dict{package: why its host module was left out} of the last hosts().
modules
Section titled “modules”def modules() -> listThe host modules of the packages this process uses, in their order (paths.packages).
def first(name: str)What the first host module that defines name has under it, or None.
require
Section titled “require”def require(name: str, what: str, provider: str)What name is in a host module, installing the package that provides provider among
its what first (as packages.auto_install says) when none has it: the app package’s
overlay for the frontends that open one.
def focus() -> boolBring a frontend’s window to the front when a conversation needs the user: each host module’s focus() in turn, until one finds its window open (the app’s, then another package’s page). Whether one did.
overlay
Section titled “overlay”def overlay()The timeline overlay (the app package’s harness_app/overlay.py), provided by an installed frontend.
