Skip to content

Loading search…

中文

Session host

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

def statics() -> dict

{package: its ui/static/ folder} of the packages this process uses that have one.

def static() -> Path | None

The 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).

def package_api(path: str)

The handler a package’s host module gives for /api/<package>/<name> (its api, harness/extensions.py), or None.

def bench_kind(bench: str, worker: dict | None=None) -> str | None

The workspace kind an experiment’s worker serves (None: one no pool starts).

def lanes_of(directory: Path) -> dict

The 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.

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() -> str

The address to open, with the session key.

def info() -> dict

What the page is told about the server, the statuses of a conversation and the words of an approval.

def touch() -> None

A client asked for something (the session host counts this as activity).

def model_changed(run: str, changed: dict) -> None

A conversation’s model or reasoning effort changed (POST /api/conversations/model).

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).

def directory(run: str) -> Path

The episode directory of run: a conversation’s this server runs, else an episode under the root (ValueError when there is none).

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).

def start_conversation(request: dict) -> str

Start an interactive conversation (see the module docstring) and return its run.

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.

class Handler(BaseHTTPRequestHandler)
def log_message(format, *args)
def send_response(code, message=None)
def do_GET()
def do_POST()
def get_options(query)
def post_conversations(body)
def post_conversations_message(body)
def post_conversations_steer(body)
def post_conversations_cancel(body)
def post_conversations_config(body)
def post_conversations_answer(body)
def post_conversations_diff(body)
def post_conversations_merge(body)
def post_conversations_discard(body)
def post_conversations_terminal(body)
def post_conversations_terminal_resize(body)
def post_conversations_pause(body)
def post_conversations_resume(body)
def post_conversations_confirm(body)
def post_conversations_model(body)
def post_conversations_close(body)
def get_sessions(query)
def get_sessions_show(query)
def post_sessions_stop(body)
def post_sessions_approve(body)
def get_info(query)
def get_libraries(query)
def get_library(query)
def get_runs(query)
def get_meta(query)
def get_events(query)
def get_lanes(query)
def get_file(query)
def get_stream(query)
class FilesHandler(SimpleHTTPRequestHandler)

The files under a directory, read-only and without a key (serve_files), with ranges.

def send_head()
def log_message(*args)
def serve_files(root: Path, host: str='127.0.0.1', port: int=8000) -> None

Serve the files under root until interrupted (harness view); print where.

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>”.

class RemoteError(RuntimeError)

The server refused a request; the message is its reason.

class HostGone(RemoteError)

The server stopped answering: no connection for GONE_SECONDS in a row.

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).

def describe() -> dict

The session this conversation belongs to, as the server lists it (its status, what it waits for, its agents); refreshes status.

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.

def send_message(text: str, files=()) -> None

A 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=()) -> None

Redirect an active turn of an external session that supports steering.

def cancel() -> None

End the turn that runs; the conversation waits for the next message.

def set_config(identifier: str, value) -> dict

Select an advertised session configuration value.

def answer(request_id: str, content: dict | None) -> None

Answer the current external form, or decline it with None.

def diff() -> dict

Review the working changes in a Git-backed external session.

def merge() -> dict

Fast-forward the original branch to a closed session’s committed work.

def discard() -> dict

Remove a closed session’s managed worktree.

def terminal_input(text: str) -> None

Send terminal input to a session started with an interactive PTY.

def terminal_resize(columns: int, rows: int) -> None

Match a hosted PTY to the attached terminal’s character dimensions.

def handoff(agent: str, context: str, *, folder=None, isolation=None)

Start another agent with the context chosen by the user.

def enable_agents() -> None

Switch sub-agents on for this conversation (off unless session.agents is on): their calls load before its next program.

def pause() -> None

Pause at the next execution step.

def resume() -> None

Go on after a pause.

def confirm(accept: bool, reason: str='', remember: str | None=None, *, request_id: str | None=None) -> None

Approve or reject the program waiting for confirmation, or the step waiting for physical input (remember: once, app, session, always).

def set_model(model: str | None=None, reasoning_effort: str | None=None) -> dict

Another main model (an id of its provider) or reasoning effort from its next call on.

def close() -> None

End the conversation.

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.

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).

def new_name(suffix: str='') -> str

A new run’s directory name: “20261102-140530-3fa9c1”, then “-<suffix>” when given.

def summary(directory) -> dict

An 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).

def ending(found: dict | None) -> str | None

How an episode ended (its summary’s status, as ENDINGS names it); None without one.

def episode_dir(root: Path, run: str) -> Path

The episode directory run (a path relative to root); ValueError when it leaves the root or has no trace.

def describe(root: Path, directory: Path) -> dict

One 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.

def list_episodes(root: Path, limit: int=500) -> list[dict]

The episodes under root, the most recently changed first (at most limit).

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).

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() -> bool

Whether the episode has its last summary (nothing more will be written).

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.

def presentation(record: dict) -> dict

A model_reply as {kind, thought, body}; one without presentation (older) read from its text.

def answer_of(record: dict) -> str | None

The agent’s answer when record is the reply that ends its turn (harness run -p), else None.

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.

def approval_options(item: dict) -> list[tuple[str | None, str]]

The approval scopes and labels offered for a transcript request.

def item_lines(item: dict) -> list[tuple[str, str]]

The plain-text lines of one transcript item, each with its role.

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().

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]

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.

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>).

def command(conversation, name: str, values: dict | None=None) -> None

Do COMMANDS[name] on conversation with the user’s values of its takes (empty ones left out); KeyError for a name that is none.

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) -&gt; str a line about this computer over the terminal chat's /settings
waiting(owner) -&gt; str | None what an agent of a session waits for (the input lock)
recording(directory) -&gt; dict a run's screen recording ({"path", "start", "stop",
"area"}), or None
areas(text) -&gt; tuple a screen area "x,y,width,height" as numbers
Chromes(directory, limit) the headless Chromes a suite's background browsers share
overlay the timeline overlay (the app package's: a window on top of
the screen; launch, for_session, for_runs, close, Options)
focus() -&gt; 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 cancellation
pages 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/&lt;package&gt;/&lt;file&gt;
api the package's API: {"inbox": handler}, served at
/api/&lt;package&gt;/inbox with the session key; handler(server,
data) -&gt; 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) -&gt; 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 closes
export_video(directory, ...) a run as a video, used by a frontend command
profiles {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).

def host_problems() -> dict

{package: why its host module was left out} of the last hosts().

def modules() -> list

The 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.

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() -> bool

Bring 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.

def overlay()

The timeline overlay (the app package’s harness_app/overlay.py), provided by an installed frontend.