bots

package
v1.801.466 Latest Latest
Warning

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

Go to latest
Published: Aug 5, 2026 License: Apache-2.0 Imports: 17 Imported by: 0

Documentation

Overview

Package bots is a bot doing your work on a real desktop, live, while you watch.

It is the whole cloud side of the headless bot: the CONTROL PLANE for a bot run — a task executed on a surface (a desktop or terminal sandbox the bot drives) with a LIVE session, the URL the hanzo.app /vnc panel embeds to watch or attach — TOGETHER WITH the door to the service that executes it, @hanzo/bot.

One product, not two. The control plane and the transport to the executor were separate apps once (apps/runtime), which made a LANGUAGE boundary look like a product boundary: the surface is Go, the executor is TS, and nothing else distinguished them. They answer for the same thing and now live in one place.

CLOUD OWNS POLICY, THE EXECUTOR OWNS THE RUN. The sandbox lives in @hanzo/bot, keyed in its own store under the tenant that started it; that store is the only thing that knows whether a run is alive. So this package keeps no second copy of it. It owns what a control plane owns — who you are, which org you are, and whether you may — and then asks the executor, which IS the registry. Copying that state into cloud would create a second id space agreeing with nothing: listing runs that do not exist and stopping runs never started.

A bot run is ONE value with ONE home. It is not the bot MACHINE that hosts an executor (visor's /v1/compute/bots — a machine you rent).

Isolation: the org is the gateway-minted X-Org-Id (HIP-0026) resolved via principal.Org, NEVER a request field, and it is what cloud sends the executor, which keys every run under tenants/{org}/. A caller cannot name another tenant's org, so it cannot read or stop another tenant's runs; a foreign run id resolves under the CALLER's org, where it does not exist, and answers 404.

Two faces, and the split between them is what a tenant can ACT on:

  • NATIVE + TYPED, the run control plane (org-scoped; the console BotsApi and the CLI `hanzo bot run` call it):

    POST /v1/bots/run -> 501: no executor launch operation exists yet GET /v1/bots -> {bots:[{runId,task,surface,status,sessionUrl,startedAt}]} POST /v1/bots/:runId/stop -> {runId, status}

  • RELAYED, the executor's own operational paths at /v1/bot/* (relay.go). A liveness probe is not a tenant-scoped resource, so it stays a relay rather than being reimplemented in Go.

The transport itself (transport.go) knows how to MOVE BYTES and nothing about what they mean: a caller states WHAT it wants done (a Call) and gets back a domain-shaped outcome — never an *http.Response, a status code, or a framing detail. Today those bytes move over HTTP; per HIP-0106/HIP-0120 they should move over ZAP, and that swap is meant to be a change to transport.go plus each caller's one stub, not a rewrite. apps/coding dispatches its coding tasks to the same executor and uses the same Call.

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrNotFound  = errors.New("runtime: no such target")
	ErrNotServed = errors.New("runtime: operation not served")
)

ErrNotFound reports that the runtime ANSWERED that it holds no such target — a structured reply from an operation that exists. It is the transport-agnostic form of "absent": a caller decides what absence MEANS for its domain, and that decision stays in the domain rather than being read off a status code.

ErrNotServed reports that the runtime does not serve the operation AT ALL — it never answered about the target, so nothing was learned about it. The two are separated because conflating them is a correctness lie: a runtime build without the operation reports absent for EVERY target, and treating that as "already gone" turns permanent failure into permanent success. Absence is only ever meaningful from a callee that could have said otherwise.

Functions

func Do added in v1.801.455

func Do(ctx context.Context, c Call) error

Do invokes c and discards any response payload — the command form. It returns ErrNotFound when the runtime answers that the target is absent, ErrNotServed when it does not serve the operation, and a bounded descriptive error otherwise. Bounded by callTimeout.

func ErrBody added in v1.801.455

func ErrBody(resp *http.Response) string

ErrBody reads a bounded prefix of a body for an error message.

func Mount

func Mount(app cloud.Router, deps cloud.Deps) error

Mount wires the bots surface onto app per HIP-0106.

func Read added in v1.801.455

func Read(ctx context.Context, c Call, out any) error

Read invokes c and decodes its answer into out — the query form. Same error vocabulary as Do. Bounded by callTimeout.

func Stream added in v1.801.455

func Stream(ctx context.Context, c Call, fn func(msg []byte)) error

Stream invokes c and hands each response message to fn as it arrives — the streaming form. Framing is ours; what a message MEANS is the caller's contract, so fn receives one encoded message and decodes it itself. A message over lineCap ends the stream with an error rather than growing the buffer. The deadline is the caller's ctx: a stream legitimately runs for minutes.

Types

type BotRun added in v1.801.350

type BotRun struct {
	// RunID is the run's id in the bot runtime, and the node id its live VNC session
	// is registered under.
	RunID string `json:"runId"`
	// Task is the instruction the bot is executing.
	Task string `json:"task"`
	// Surface is what the bot drives: the desktop or terminal sandbox it runs in.
	Surface string `json:"surface"`
	// Status is the run's state as the runtime reports it; "running" when the runtime
	// names none of its own.
	Status string `json:"status"`
	// SessionURL is the live session the hanzo.app /vnc panel embeds to watch or
	// attach to this run. Derived here from the run id, never sent by the runtime.
	SessionURL string `json:"sessionUrl"`
	// StartedAt is when the run began, RFC 3339, as the runtime stamped it.
	StartedAt string `json:"startedAt"`
}

BotRun is one row of GET /v1/bots — the console list item. sessionUrl is derived control-plane side from runId (the ONE place a session URL is built), so the runtime never has to know its own public origin.

The name is qualified because the fleet's schema namespace is FLAT and apps/visor already publishes a `botView` for a bot MACHINE (a box you rent). This is a bot RUN. Two values, two names.

type BotRuns added in v1.801.350

type BotRuns struct {
	// Bots is the org's live runs. Always an array, never null.
	Bots []BotRun `json:"bots"`
}

BotRuns is the GET /v1/bots envelope; Bots is always non-nil so an org with no runs serializes as {"bots":[]}, never {"bots":null}.

type BotStopped added in v1.801.350

type BotStopped struct {
	// RunID is the run that was stopped.
	RunID string `json:"runId"`
	// Status is the run's terminal state: "stopped".
	Status string `json:"status"`
}

BotStopped is the POST /v1/bots/{runId}/stop receipt.

type Call added in v1.801.455

type Call struct {
	Op     string
	Org    string
	User   string
	Body   any
	Secret bool
}

Call is one operation on the runtime.

Op addresses the operation. It is the caller's own identity for what it is invoking and is opaque here — the transport carries it, it never interprets it. (Under HTTP it is a path; under ZAP it becomes a generated method id. Either way it belongs to the domain's stub, which is why runtime holds no table of operations and therefore no domain knowledge.)

Org/User are the tenant context cloud has ALREADY resolved and authorized. Body is the payload, encoded by the transport; nil sends none. Secret declares that Body carries a credential, which forbids a cleartext hop.

type Run

type Run struct {
	ID        string
	Task      string
	Surface   string
	Status    string
	StartedAt string // RFC3339, as the runtime stamps it
}

Run is one bot run as the runtime reports it.

type Runtime

type Runtime interface {
	List(ctx context.Context, org string) ([]Run, error)
	Stop(ctx context.Context, org, runID string) error
}

Runtime is the seam onto the run registry — the bot runtime, which owns the sandboxes and is therefore the only truthful answer to "what is running". Bound to the real transport in wire.go; a fake in tests.

Every method takes org FIRST and the runtime scopes by it. The seam carries no authority: cloud decides WHETHER a caller may ask, the runtime answers WHAT it holds for that org.

Jump to

Keyboard shortcuts

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