Help us shape the future of Go! Take the 2026 Go Developer Survey

harness

package
v0.5.8 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 28, 2026 License: MIT Imports: 31 Imported by: 0

Documentation

Overview

Package harness steps a session: load the file, build the context, call the provider, append what comes back, execute the calls, repeat until the model stops calling tools.

Index

Constants

View Source
const MaxName = 100

MaxName bounds a session's name: a title a list shows on one line.

View Source
const MaxPost = 8 << 20

MaxPost bounds one post: a paragraph and a screenshot, typed or pasted by a person. A file goes to a worker, not into the state file.

Variables

This section is empty.

Functions

func Handler

func Handler(ctx context.Context, h *Harness) http.Handler

Handler is the harness's HTTP surface: ten verbs on a session, two reads of the environment, and removing a worker. Records go out in the state file's own bytes, one after another.

GET  /sessions                    ?status= ?limit= ?after=<id>                 -> [Info], newest updated first
POST /sessions                    Create body; ?via= required with a name      -> {"id"}
GET  /sessions/{id}               -> Info
GET  /sessions/{id}/records       ?after=N  ?wait=30s to long poll             -> records
POST /sessions/{id}/records       one body, or multipart; ?via= required,      -> {"n"}
                                  who is posting. one message record per
                                  part, each with its Content-Type. With
                                  an Idempotency-Key header, a repost
                                  appends only the parts not yet held
                                  under that key and returns the post's n
POST /sessions/{id}/retry         step from the tip, whatever it is            -> {"status"}
POST /sessions/{id}/stop          ?via= required. cut a running step, write    -> 204; the records
                                  it down; kill its environments and jobs        say what happened
PUT  /sessions/{id}/workers       [{"name"}]; ?via= required. a config record  -> {"n"}
                                  with these workers; the next step sees them
PUT  /sessions/{id}/name          the name as text; ?via= required. a name     -> {"n"}
                                  record; empty removes the name
DELETE /sessions/{id}             ?via= required. the file is gone for good;   -> 204
                                  a running step is stopped first
GET  /workers                     -> connected workers' hellos, and refused
                                     ones with the reason
DELETE /workers/{name}            the worker leaves the mesh for good; its     -> 204
                                  device code still joins new machines
GET  /providers                   -> [{"name","shape","url"}]: what this harness
                                     serves as name/model; no secrets

ctx outlives every request: it is what steps run under.

func System

func System(model string) string

System is the system prompt for a model.

func Tools

func Tools(name string, input []string) []provider.Tool

Tools is the tool set a config record names. Every tool takes a worker. The read description names the media types the session's input lists.

Types

type Create

type Create struct {
	From         string                     `json:"from,omitempty"`
	N            int                        `json:"n,omitempty"`
	Model        string                     `json:"model,omitempty"`
	ContextLimit int                        `json:"context_limit,omitempty"`
	Workers      []statefile.Worker         `json:"workers,omitempty"`
	Tools        string                     `json:"tools,omitempty"`
	Input        []string                   `json:"input,omitempty"`
	Params       map[string]json.RawMessage `json:"params,omitempty"`
	Preset       string                     `json:"preset,omitempty"`
	Name         string                     `json:"name,omitempty"`
}

Create is the body of POST /sessions. From set is a copy of records 1 through N-1 of that session; otherwise a fresh session from Model, ContextLimit, and Workers. Workers are not checked: the config is the whitelist, and a worker named before it dials in is a worker the session waits for.

type Harness

type Harness struct {
	Sessions  statefile.Sessions
	Providers map[string]provider.Provider // read and replaced under pmu once the harness runs
	Workers   *Pool
	Remove    func(name string) error // the coordinator's: the worker leaves the mesh for good
	// contains filtered or unexported fields
}

func (*Harness) Changed

func (h *Harness) Changed(id string) <-chan struct{}

Changed is a channel closed at the session's next append.

func (*Harness) Create

func (h *Harness) Create(ctx context.Context, c Create, via string) (string, error)

Create makes a session and returns its id. A fresh one starts with the system prompt, the AGENTS.md scan, and the worker list, so all three reach the provider as system text ahead of the first message. A copy is byte for byte and gets nothing appended: it already holds both.

func (*Harness) Delete added in v0.5.4

func (h *Harness) Delete(ctx context.Context, id, via string) error

Delete stops the session, then removes its file for good. Append-only is of records: none is ever changed, and a session goes whole. It waits for the stopped step to end, so no step writes to a file that is gone; under the lock a post takes, so no post does. Copies keep their own files, workers their logs.

func (*Harness) Info

func (h *Harness) Info(id string) (Info, error)

Info reads one session's file.

func (*Harness) Kick

func (h *Harness) Kick(ctx context.Context, id string)

Kick steps a session in the background until it is not open. A kick during a run makes the runner look once more before it stops, so a message that lands as a run ends is not left waiting. Each pass gets its own context so a stop ends one pass and not the next. A pass that ends closed or failed copies the file to the session's workers.

func (*Harness) List

func (h *Harness) List() ([]Info, error)

List is every session, newest updated first, from the index. Entries a write touched since the last list are read again; the rest are served from memory. The mark is cleared before the read, so a write that lands during the read marks it again. A file that does not read is logged and left out until its next write. Resume fills the index on start, and touch adds a created session.

func (*Harness) Post

func (h *Harness) Post(ctx context.Context, id, via, key string, parts []Part) (int, error)

Post appends one message record per part under one lock and kicks the session once, so a text and an image reach the model as one turn. The append is the acknowledgement: a closed session starts stepping on it, a stepping one sees it at its next context build. Returns the last n. A post with a key appends only the parts the file does not hold with that key, so a connector that lost the reply posts again and the post ends whole, once.

func (*Harness) Resume

func (h *Harness) Resume(ctx context.Context) error

Resume kicks every session that needs a step. This is the open-files index, rebuilt from the tips on start; a failed session waits for a run or a message. A file that does not read is logged and left out, not a reason to stay down.

func (*Harness) Retry

func (h *Harness) Retry(ctx context.Context, id string)

Retry steps a failed session from its tip: after a stop it continues, after exhausted provider retries it tries again, past the limit it gets the same error again. Nothing is cleared.

func (*Harness) Run

func (h *Harness) Run(ctx context.Context, id string) (statefile.Status, error)

Run steps until the session is not open.

func (*Harness) SetName added in v0.5.3

func (h *Harness) SetName(id, via, n string) (int, error)

SetName appends a name record under the lock a post takes. An empty name removes the session's name. The model never sees it and status is unchanged.

func (*Harness) SetProviders added in v0.4.0

func (h *Harness) SetProviders(ps map[string]provider.Provider)

SetProviders replaces the providers: a session's next step uses the new ones, a step running keeps its own.

func (*Harness) SetWorkers added in v0.5.0

func (h *Harness) SetWorkers(id, via string, workers []statefile.Worker) (int, error)

SetWorkers appends a config record that is the newest one with these workers, under the lock a post takes. Nothing else in the config changes. A step running keeps the config it started with; the next step lists the new workers to the model as a change.

func (*Harness) Step

func (h *Harness) Step(ctx context.Context, id string) (status statefile.Status, err error)

Step runs one cycle and returns the status after it. A failed session is stepped only when a run asked for it; every step spends the ask, so a run on a closed session does not carry over to a later failure. Interrupted sessions get a result for every orphan call and become open; the next cycle steps. Past the context limit nothing is called: the session's environments are killed, the error says so, and the session is failed. A retry of a failed session adds resume as a message from retry. The worker list enters as a system record whenever it differs from the last one the model saw.

func (*Harness) Stop

func (h *Harness) Stop(ctx context.Context, id, via string) error

Stop ends everything the session is doing. A running step is cut now: the provider stream is cut, every call in flight is answered "stopped, effect unknown", a call of a recorded step not yet started is answered "not run", and the last record is an error naming via; a session not stepping gets a system record from stop instead. Either way every environment and job the session has on its connected workers is killed.

type Info

type Info struct {
	ID          string           `json:"id"`
	Name        string           `json:"name,omitempty"`
	Status      statefile.Status `json:"status"`
	Config      statefile.Conf   `json:"config"`
	Created     time.Time        `json:"created"`
	Updated     time.Time        `json:"updated"`
	N           int              `json:"n"`
	ContextUsed int              `json:"context_used"`
	Usage       statefile.Usage  `json:"usage"`
}

Info is what GET /sessions/{id} and each entry of GET /sessions say about a session: everything the tip states and its name, no cost. ContextUsed is the last step's prompt in tokens, against Config.ContextLimit. Updated is the newest record that is not a name, so a rename does not reorder a list.

type Listed

type Listed struct {
	worker.Header
	Refused string `json:"refused,omitempty"`
}

Listed is a worker as GET /workers shows it: its hello, and why its last connection was refused, when it was.

type Output

type Output struct {
	Body []byte
	Exit int
	Cut  int
	Full string
}

Output is what a run left: the tail the worker's side kept, the exit code, the lines dropped before the tail, and the file on the worker holding all of it when the output went over the limits.

type Part

type Part struct {
	Type string
	Body []byte
}

Part is one body of a post: its content type and bytes.

type Pool

type Pool struct {
	// contains filtered or unexported fields
}

Pool is the set of workers connected right now, keyed by the name the coordinator gave each one's node. Workers dial in over the mesh and are known the moment they say hello; a lost connection forgets the worker until it dials again.

func NewPool

func NewPool(desired func(string) worker.Desired) *Pool

func (*Pool) Add

func (p *Pool) Add(r *Remote)

func (*Pool) Drop added in v0.3.0

func (p *Pool) Drop(name string)

Drop hangs up on the worker named name, when it is connected.

func (*Pool) Get

func (p *Pool) Get(name string) (*Remote, bool)

func (*Pool) List

func (p *Pool) List() []Listed

List is every connected worker and every refused one not connected now, sorted by name.

func (*Pool) Resend added in v0.5.0

func (p *Pool) Resend()

Resend sends each connected worker its drives when they changed: after the config, or the workers on the mesh, did.

func (*Pool) Serve added in v0.3.0

func (p *Pool) Serve(l net.Listener, node func(netip.Addr) (string, bool)) error

Serve holds every worker that dials l, the harness's own node on the mesh, until l closes. WireGuard admits only nodes the coordinator gave keys to, and the source address is checked against node, the coordinator's registry, which also names the worker, so no secret rides the connection and no worker can take another's name. A worker that dials again under a name still held replaces the old entry: the old connection is dead or dying, and the new one is the worker as it is now.

type ProviderInfo added in v0.5.4

type ProviderInfo struct {
	Name  string `json:"name"`
	Shape string `json:"shape"`
	URL   string `json:"url"`
}

ProviderInfo is a provider as GET /providers shows it: no key.

type Remote

type Remote struct {
	worker.Header
	// contains filtered or unexported fields
}

Remote is a worker on the far end of a connection. Its hello header says what it is. Requests are multiplexed by id; a call waits for its terminal frame, the connection's end, or ctx: the worker's timeout bounds a run, and a stop cuts it short.

func Connect

func Connect(conn net.Conn, admit func() (string, error)) (*Remote, error)

Connect reads the worker's hello, checks the connection with admit, whose error is why it is refused and whose name is the worker's, and starts routing its replies. A worker from another release is told to update to this one and refused: it comes back as this release, and the error says whether the update began or why it failed. That worker is returned with the error, so the pool can list it.

func (*Remote) Drives added in v0.5.0

func (r *Remote) Drives() []worker.Drive

Drives is every drive the worker serves or mounts, as it last said.

func (*Remote) Job added in v0.5.5

func (r *Remote) Job(ctx context.Context, session, interpreter, code, job string, done func(Output, error)) (int, string, error)

Job starts code as a background job named job and returns its pid and log once the worker runs it. The call stays open until the job ends: done gets the log's tail and exit, or the error that ended the call, such as a lost connection. A job a kill ended answers done, and done is not called: a stop ended it on purpose.

func (*Remote) Kill

func (r *Remote) Kill(ctx context.Context, session string) error

func (*Remote) Read

func (r *Remote) Read(ctx context.Context, path string) ([]byte, error)

func (*Remote) Run

func (r *Remote) Run(ctx context.Context, session, interpreter, code string, timeout int) (Output, error)

Run streams the output and keeps a tail within the result limits. Past twice the byte limit the front is dropped at a line start and the lines counted, a line cut in the middle counting as one; the worker has the whole in the file the result names. timeout is in seconds, and zero is runTimeout.

func (*Remote) Update added in v0.5.6

func (r *Remote) Update(ctx context.Context, cmd, v string) error

Update has the worker make cmd beside it release v.

func (*Remote) Write

func (r *Remote) Write(ctx context.Context, path string, data []byte) error

type Worker

type Worker interface {
	Run(ctx context.Context, session, interpreter, code string, timeout int) (Output, error)
	Job(ctx context.Context, session, interpreter, code, job string, done func(Output, error)) (int, string, error)
	Read(ctx context.Context, path string) ([]byte, error)
	Write(ctx context.Context, path string, data []byte) error
	Kill(ctx context.Context, session string) error
}

Worker is the three primitives every worker has, Job, and Kill. Tools are built on the primitives here. Write creates missing parents. Run's environment persists per session and interpreter until Kill; the harness decides when a session's environments stop being worth keeping. A job runs in an interpreter of its own until its code ends or Kill.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL