AgentSession.start() and
chat(), with the standard ports. This page is for a host — a program that
supplies its own output sink, tools, prompts, error reporting or gather
progress, and drives one agent across many turns. The gateway (Slack, Discord,
Telegram) and the interactive shell are both hosts, and they are the same
program with different ports.
Three roles, four import doors
Everything a host touches comes through four modules. Nothing else undercore.agent_harness is public.
The host loop
Build once, then one call per inbound message:DefaultPorts holds what does not vary per host — reasoning client, run
records, the default tool provider and prompt provider — and takes only the
inputs those defaults need (console, logger, surface). Every port you pass
to agent() replaces that default. TurnBinding is a value, not a set of
keyword arguments: a field you omit is this turn’s value (no hooks, no
callback), never “leave alone”, so a pooled agent cannot inherit another
conversation’s hooks by omission. handle is the only per-message call a host
makes; dispatch (one engine turn) sits underneath it.
The in-memory family is HeadlessPorts — a script or test says
HeadlessPorts().agent(tools=NullToolProvider()) and has an agent with zero
configuration. There is one construction verb, <family>.agent(...); hosts do
not call HeadlessAgent(...) directly.
The same loop in the two shipped hosts
The gateway keeps one agent per logical session and callshandle per inbound
message:
handle per prompt:
What you implement vs what you call
Rules a host must keep
- One agent per logical session; one turn at a time on it.
bind_turnmutates per-turn state; the gateway holds a per-session lock acrossdispatch. Do not share one agent between concurrent turns. - State the whole turn. Build a fresh
TurnBindingper message and pass it tohandle; do not callbind_turn/dispatchyourself. - Bind on the worker thread you dispatch from. Storage scope is a
ContextVar; if you hand the turn to a thread pool, wrap it withconfig.scope_context.in_current_scopeon the calling thread. - Do not reach past the doors. Modules under
core.agent_harness.turns,.tools,.sessionand.promptsare internal; the shipped hosts pin their remaining deep imports in shrink-only tests.