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

runtime

package
v0.20.2 Latest Latest
Warning

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

Go to latest
Published: Oct 27, 2026 License: MIT Imports: 18 Imported by: 0

Documentation

Overview

Package runtime is the runtime link's server side (WEFT-PLAYGROUND §10.3, S4.2): the registry of connected runtimes and the three routes a weft/runtime client speaks to — register, the SSE command stream, acks. Studio decides (the playground routes in studio/playground.go enqueue commands against a registered agent); the runtime in your app executes.

The package is self-contained on purpose: it reads no run data — a command's validation data is the runtime's own registration, refreshed on every reconnect, because the runtime's copy is the authoritative one (§10.4). Its one database touch is the optional write-through of its own bookkeeping (WithStore, below). Commands are at-most-once: the runtime acks before executing and ignores repeated ids; Studio refuses a reused command id outright (409) and never re-sends a lost one — the user re-issues it (§10.5).

Command lifecycle (§10.5), with its two timers:

queued ──(ack)──► accepted ──(finish)──► finished
   │                 │
   ├──(reject)──► rejected
   └──(no ack in 30 s, or the stream died before the ack)──► lost
accepted ──(stream died, no finish in 10 min)──► lost
accepted ──(no finish and no heartbeat in max(10 min, 3 beats))──► lost

The second accepted edge is for a runtime that heartbeats (Registration.HeartbeatMS): while it holds the command it re-posts the accepted ack at that cadence, and each repeated accepted ack restarts the watch — on an accepted row it is liveness, on a lost one a 409, on a finished one a no-op.

A late ack or finish still lands: the truth about a run wins over a timer's guess.

Built WithStore (studio.New passes its database when it implements obsdb.CommandStore), the server writes all of it through and a restarted Studio loads it back, deciding "lost" from the stored state — never re-sending an accepted command (store.go has the rules). Without a store everything lives in memory and a restart forgets it.

Runtime ids are the runtime's to choose: weft/runtime derives one stable per (service, cwd, agent set), so an app restarted in the same place registers under the same id and a link to /runtimes/<id> survives it. A registration under an id another process holds a live stream on is answered with `<id>-<pid>` (RegisterResponse.RuntimeID, which the runtime adopts): two replicas of one service in one cwd get distinct ids, a reconnect of the same process keeps its own.

Index

Examples

Constants

View Source
const (
	VerbDecide = "decide"
	VerbSteer  = "steer"
	VerbCancel = "cancel"
)

The session frame's verbs.

View Source
const (
	StateQueued   = "queued"
	StateAccepted = "accepted"
	StateRejected = "rejected"
	StateFinished = "finished"
	StateLost     = "lost"
)

Command states (§10.5).

View Source
const (
	BreakpointsByStudio  = "studio"
	BreakpointsByRuntime = "runtime"
	BreakpointsByStore   = "store"
)

Where a runtime's breakpoint set came from (RuntimeView.BreakpointsBy).

View Source
const BreakpointsSurfaceAPI = "api"

BreakpointsSurfaceAPI is RuntimeView.BreakpointsSurface for a PUT that named no surface (a script, curl).

View Source
const ReasonScriptedMiss = "scripted_miss"

ReasonScriptedMiss is Reason.Kind for the scripted engine's miss: no recorded turn answered the replay's request.

Variables

View Source
var (
	// ErrUnknownRuntime: no runtime ever registered under the id.
	ErrUnknownRuntime = fmt.Errorf("studio/runtime: unknown runtime")
	// ErrNotConnected: the runtime registered once but holds no live
	// command stream now.
	ErrNotConnected = fmt.Errorf("studio/runtime: runtime not connected")
	// ErrConnected: Forget of a runtime that holds a live command
	// stream — only a runtime that is gone is forgotten.
	ErrConnected = fmt.Errorf("studio/runtime: runtime is connected")
	// ErrRuntimeBusy: Forget of a runtime a queued or accepted command
	// is still addressed to.
	ErrRuntimeBusy = fmt.Errorf("studio/runtime: runtime has live commands")
	// ErrDuplicateCommand: the command id was already used (409, the
	// at-most-once rule's Studio side).
	ErrDuplicateCommand = fmt.Errorf("studio/runtime: command id already used")
	// ErrUnknownCommand: no command under the id.
	ErrUnknownCommand = fmt.Errorf("studio/runtime: unknown command")
	// ErrInvalidCommandID: a caller-supplied command id that is not 1
	// to 128 characters of [A-Za-z0-9._:-] (400). The id is written
	// verbatim as the SSE frame's id line, so anything that could break
	// a line — and with it forge a frame on the runtime's stream — is
	// refused before it reaches a row or the wire.
	ErrInvalidCommandID = fmt.Errorf("studio/runtime: invalid command id")
	// ErrUnknownRun: no runtime started the run (CancelRun) — the app's
	// own turns, or an id no ack ever named.
	ErrUnknownRun = fmt.Errorf("studio/runtime: no runtime started the run")
	// ErrRunFinished: the run's command is over — finished (a parked
	// run included: approval is a run boundary), rejected or lost — so
	// there is nothing in flight to cancel (CancelRun).
	ErrRunFinished = fmt.Errorf("studio/runtime: the run's command is no longer in flight")
)

Errors Enqueue and the routes answer with.

Functions

func ValidCommandID

func ValidCommandID(id string) bool

ValidCommandID reports whether id may be used as a command id: 1 to 128 characters of [A-Za-z0-9._:-] (the minted "cmd_" + ULID form is inside it). The id doubles as the SSE frame id, so a control character in it would write the caller's own fields onto the runtime's command stream.

Types

type Ack

type Ack struct {
	CommandID string `json:"command_id"`
	State     string `json:"state"`
	RunID     string `json:"run_id,omitempty"`
	Status    string `json:"status,omitempty"`
	Error     string `json:"error,omitempty"`
	// Reason is a finished ack's failure typed by the runtime (absent
	// from a runtime older than the field, and for any failure it does
	// not type): kind "scripted_miss", the source step, the key
	// components. CommandStatus.Reason prefers it to reading Error.
	Reason *AckReason `json:"reason,omitempty"`
}

Ack is the POST /api/runtime/acks body: accepted/rejected before execution, finished at the run's end — Status succeeded | failed, and Error why (a rejection's reason, a failed run's error).

type AckReason added in v0.19.0

type AckReason struct {
	Kind       string   `json:"kind"`
	Step       int      `json:"step"`
	Components []string `json:"components"`
}

AckReason is Ack.Reason: what the runtime knows of why its run failed — Reason's fields without Studio's sentence.

type AgentDefaults added in v0.13.0

type AgentDefaults struct {
	MaxSteps    int    `json:"max_steps"`
	Parallelism int    `json:"parallelism"`
	Thinking    string `json:"thinking"`
	// ThinkingBudget is the agent's default thinking budget in tokens
	// (absent: none): a budget alone sets the request's thinking.
	ThinkingBudget int64      `json:"thinking_budget,omitempty"`
	Temperature    *float64   `json:"temperature,omitempty"`
	TopP           *float64   `json:"top_p,omitempty"`
	MaxTokens      *int       `json:"max_tokens,omitempty"`
	Seed           *int64     `json:"seed,omitempty"`
	Stop           []string   `json:"stop,omitempty"`
	ToolChoice     ToolChoice `json:"tool_choice"`
}

AgentDefaults are an agent's run defaults in the command's override vocabulary: its caps, its thinking level ("" is the provider default), the sampling knobs its core.Params set (absent: the adapter's own) and its tool choice (mode "auto" when none was set).

type AgentLimits

type AgentLimits struct {
	MaxSteps    int `json:"max_steps"`
	Parallelism int `json:"parallelism"`
}

AgentLimits are the agent's own lower-only bounds.

type AgentRegistration

type AgentRegistration struct {
	Name     string `json:"name"`
	Manifest string `json:"manifest"`
	// ManifestHash is sha256 (lowercase hex) of Manifest's bytes as the
	// runtime registered them — the weft.manifest.hash its own runs of
	// the agent carry. A runtime older than the field sends none, and
	// Studio hashes Manifest itself (AgentView.ManifestHash).
	ManifestHash string            `json:"manifest_hash,omitempty"`
	Models       []string          `json:"models"`
	Resolver     bool              `json:"resolver"`
	Limits       AgentLimits       `json:"limits"`
	Defaults     AgentDefaults     `json:"defaults"`
	SideEffects  map[string]string `json:"side_effects"`
	Allow        []string          `json:"allow"`
}

AgentRegistration is one exposed agent. Resolver says the runtime holds a runtime.ModelResolver (a command may name a model outside Models; the runtime decides); Defaults are the agent's run defaults (plan F3's option lab greys them). A runtime older than either sends neither: false and the zero value.

func (AgentRegistration) Hash added in v0.19.0

func (a AgentRegistration) Hash() string

Hash is the agent's manifest hash: the one its registration carried, else sha256 of its Manifest bytes (the core's weft.manifest.hash of the same bytes) — "" with no manifest.

func (AgentRegistration) IsAllowed

func (a AgentRegistration) IsAllowed(tool string) bool

IsAllowed reports whether tool is on the agent's AllowSideEffects list.

func (AgentRegistration) ManifestInstructions

func (a AgentRegistration) ManifestInstructions() string

ManifestInstructions returns the agent's registered system prompt — what an experiment drawer pre-fills from (the code's own words, not a guess from the trace).

func (AgentRegistration) ManifestModelName

func (a AgentRegistration) ManifestModelName() string

ManifestModelName returns the agent's own model name.

func (AgentRegistration) ManifestModelProvider added in v0.19.0

func (a AgentRegistration) ManifestModelProvider() string

ManifestModelProvider returns the agent's own model provider (its ModelInfo.Provider: "wefttest" for a wefttest script).

Example

ExampleAgentRegistration_ManifestModelProvider reads the provider of a registered agent's own model: "wefttest" says engine live calls the app's test script, not a provider (GET /api/runtimes' model_provider, UX-C-09).

package main

import (
	"fmt"

	"github.com/weftgo/weft/studio/runtime"
)

func main() {
	a := runtime.AgentRegistration{Name: "director",
		Manifest: `{"weft":1,"agents":[{"name":"director","model":{"provider":"wefttest","name":"script"}}]}`}
	fmt.Println(a.ManifestModelProvider(), a.ManifestModelName())
}
Output:
wefttest script

func (AgentRegistration) ManifestToolNames

func (a AgentRegistration) ManifestToolNames() []string

ManifestToolNames lists the agent's tools in manifest order.

func (AgentRegistration) ManifestToolSchemas added in v0.13.0

func (a AgentRegistration) ManifestToolSchemas() (schemas map[string]json.RawMessage, ok bool)

ManifestToolSchemas returns the agent's tools' input schemas by name, as its manifest records them — what the runtime checks a tool_args edit against (its agent's own tools). ok is false when the manifest does not read; a tool listed without a schema is absent from the map.

type AgentView

type AgentView struct {
	Name string `json:"name"`
	// ManifestHash is the registered agent's weft.manifest.hash: what a
	// recorded run's manifest_hash is compared with to say whether the
	// run was recorded by this build of the agent (UX-C-06). "" when
	// the registration carried no manifest.
	ManifestHash string `json:"manifest_hash"`
	// ModelProvider is the agent's own model's provider as its manifest
	// names it ("" when it names none): "wefttest" says engine live
	// calls the app's test script, not a provider (UX-C-09).
	ModelProvider string   `json:"model_provider,omitempty"`
	Models        []string `json:"models"`
	Resolver      bool     `json:"resolver"`
	// Defaults are the agent's run defaults as its runtime registered
	// them; nil (absent on the wire) for a runtime older than the
	// option lab, which registers none — a client greys the new knobs
	// on that, as Studio refuses them (playground.go).
	Defaults *AgentDefaults `json:"defaults,omitempty"`
	Tools    []ToolView     `json:"tools"`
	// Instructions is the agent's registered system prompt, read from
	// its manifest: the experiment drawer pre-fills from the registered
	// config, never a guess from the trace (WEFT-PLAYGROUND §3).
	Instructions string `json:"instructions,omitempty"`
}

AgentView is one agent of a connected runtime. Resolver and Defaults are the registration's (AgentRegistration): whether a model name outside Models may be proposed, and the run defaults the option lab shows greyed beside each override.

type ApprovalDecision

type ApprovalDecision struct {
	CommandID string `json:"command_id"`
	RunID     string `json:"run_id"`
	CallID    string `json:"call_id"`
	Decision  string `json:"decision"` // approve | deny | resolve
	Reason    string `json:"reason,omitempty"`
	Content   string `json:"content,omitempty"`
	// Actor names who decided (§6 rule 4's spirit); carried for the
	// record, the runtime logs it.
	Actor string `json:"actor,omitempty"`
	// PublicID is the public id the decided run is scoped to — the
	// server's bookkeeping, never on the wire. The approval's command
	// row carries it (a panel token polls its own decision) and so does
	// the resumed run its acks name. Empty takes the run's recorded
	// scope.
	PublicID string `json:"-"`
}

ApprovalDecision is an `event: approve` frame's data (WEFT-DEVTOOLS §8.2's continue / skip / resolve): a human decision on one parked call of a run the runtime started. The runtime resumes the parked run with the matching core verb — Approve runs the handler, Deny skips it, Resolve pastes a result computed outside the process (ADR 0007).

type Breakpoints

type Breakpoints struct {
	Tools []string `json:"tools"`
}

Breakpoints is an `event: breakpoints` frame's data (§8.3): the runtime passes the named tools as core.ParkOn on every run it starts from then on [D7] — it cannot alter agents it did not build. An empty set clears.

type Budget

type Budget struct {
	MaxTokensPerExperiment int64 `json:"max_tokens_per_experiment"`
	MaxRunsPerExperiment   int64 `json:"max_runs_per_experiment"`
}

Budget is the runtime's per-experiment caps.

type CancelTarget added in v0.14.0

type CancelTarget struct {
	RunID     string `json:"run_id"`
	CommandID string `json:"command_id"`
	RuntimeID string `json:"runtime_id"`
}

CancelTarget is what CancelRun asked to stop: the run, the command that holds it in its runtime, and that runtime.

type Command

type Command struct {
	CommandID       string           `json:"command_id"`
	Agent           string           `json:"agent"`
	Source          *SourceSpec      `json:"source"`
	Input           *string          `json:"input"`
	Overrides       Overrides        `json:"overrides"`
	TranscriptEdits []TranscriptEdit `json:"transcript_edits"`
	Engine          string           `json:"engine"`
	SideEffects     string           `json:"side_effects"`
	Thread          string           `json:"thread"`
	ExperimentID    string           `json:"experiment_id"`
	Actor           string           `json:"actor"`
	PublicID        string           `json:"public_id"`

	// Runtime is the runtime the command was enqueued to.
	Runtime string `json:"-"`
	// contains filtered or unexported fields
}

Command is one `event: run` frame's data (§10.3). Runtime, seq and cancel are the server's own bookkeeping and stay off the wire.

type CommandStatus

type CommandStatus struct {
	CommandID string `json:"command_id"`
	State     string `json:"state"`
	RunID     string `json:"run_id"`
	// Status is the finished run's own outcome, succeeded | failed, as
	// the runtime's finished ack named it; absent until the command is
	// finished.
	Status string `json:"status,omitempty"`
	// Error is why, when there is a why: a failed run's error text, a
	// rejection's reason, what made the command lost. Null otherwise.
	Error *string `json:"error"`
	// Reason is Error read: the scripted engine's miss with the source
	// step and the key components it names, and Studio's sentence for
	// both surfaces (ReasonOf). Absent for any other error.
	Reason  *Reason   `json:"reason,omitempty"`
	Created time.Time `json:"created"`
	Updated time.Time `json:"updated"`
}

CommandStatus is the GET /api/playground/commands/{id} row (§10.4).

type ManifestAgent added in v0.11.0

type ManifestAgent struct {
	Name string `json:"name"`
	// ManifestHash is sha256 of the agent's own one-agent manifest —
	// the weft.manifest.hash its runs carry.
	ManifestHash string `json:"manifest_hash"`
	// Entry is the agent's object in its manifest's agents array.
	Entry json.RawMessage `json:"-"`
}

ManifestAgent is one agent of a registered manifest.

type ManifestSource added in v0.11.0

type ManifestSource struct {
	Service string `json:"service"`
	// ManifestHash is sha256 (lowercase hex) of the registration's
	// agents as one core.Manifest document — the bytes
	// weft.Manifest(agents...) writes for the same agents in the same
	// order, so a weft.json generated from them hashes the same.
	ManifestHash string `json:"manifest_hash"`
	// Live: a runtime holding a command stream now registered with
	// this manifest. False is remembered, never live.
	Live bool `json:"live"`
	// RegisteredAt is the latest registration with this manifest.
	RegisteredAt time.Time `json:"registered_at"`
	// RuntimeID is the runtime that registered it last.
	RuntimeID string          `json:"runtime_id"`
	Agents    []ManifestAgent `json:"agents"`
	// Version is the manifest documents' "weft" format version.
	Version int `json:"-"`
}

ManifestSource is one remembered registration manifest.

type Option added in v0.15.0

type Option func(*RuntimeServer)

Option configures New.

func WithStore added in v0.15.0

func WithStore(s obsdb.CommandStore) Option

WithStore makes the server write its commands, routes, breakpoint sets and registrations through to s, and loads what s holds at New — a restarted Studio over the same database keeps them (plan I3). Nil keeps the server in memory only, as without the option. One Studio per command store: two Studios sharing one (a ClickHouse database) never see each other's acks and beats, so a restarted one would load the other's queued and accepted rows and decide them lost under it (nothing runs twice; live commands read lost).

type Overrides

type Overrides struct {
	Instructions string             `json:"instructions,omitempty"`
	ToolsEnabled []string           `json:"tools_enabled,omitempty"`
	Model        string             `json:"model,omitempty"`
	Thinking     string             `json:"thinking,omitempty"`
	Options      map[string]float64 `json:"options,omitempty"`
	Params       *Params            `json:"params,omitempty"`
	ToolChoice   *ToolChoice        `json:"tool_choice,omitempty"`
	ParkOn       []string           `json:"park_on,omitempty"`
	OnlyTools    []string           `json:"only_tools,omitempty"`
}

Overrides are the experiment's changes (§5.1): the numeric options (max_steps, parallelism, temperature) and, beside them, the option lab's typed knobs (plan F3) — every one narrowing or neutral.

type Params added in v0.13.0

type Params struct {
	TopP      *float64 `json:"top_p,omitempty"`
	MaxTokens *int     `json:"max_tokens,omitempty"`
	Stop      []string `json:"stop,omitempty"`
	Seed      *int64   `json:"seed,omitempty"`
}

Params is the sampling override beside options.temperature: the rest of core.RequestParams. Absent fields keep the agent's own — a command cannot clear the agent's stop or max_tokens.

type Reason added in v0.19.0

type Reason struct {
	Kind       string   `json:"kind"`
	Step       int      `json:"step"`
	Components []string `json:"components"`
	Message    string   `json:"message"`
}

Reason is an error read: its kind, the source step it names (-1: none — the source recorded no turn), the scripted key components that differ from the recording (tools, thinking, tool_choice, sequential, messages; unanswered and exhausted say why no component does), and Studio's sentence naming the step, the components and the remedy.

func ReasonOf added in v0.19.0

func ReasonOf(errText string) *Reason

ReasonOf reads an error text: a scripted miss (weft/runtime's wording, the step and the components) becomes its Reason; any other text, or none, is nil.

Example

ExampleReasonOf reads a failed scripted command's error — weft/runtime's miss, wrapped by the run error — into the structured why and the one sentence both Studio surfaces render.

package main

import (
	"fmt"

	"github.com/weftgo/weft/studio/runtime"
)

func main() {
	r := runtime.ReasonOf("weft: run failed at step 0: model stream: weft/runtime: no recorded turn for step 2 " +
		"(scripted engine, key 700b66abcf4561ab): the tool list differs from the recording [tools]; " +
		"the scripted engine answers only what the source recorded: run on live")
	fmt.Println(r.Kind, r.Step, r.Components)
	fmt.Println(r.Message)
	fmt.Println(runtime.ReasonOf("weft: run failed at step 0: boom") == nil)
}
Output:
scripted_miss 2 [tools]
scripted has no recorded answer for step 2: the tool list differs from the recording — run on live
true

func ScriptedMissReason added in v0.19.0

func ScriptedMissReason(step int, components []string) *Reason

ScriptedMissReason is the scripted miss at the source's step (-1: none named) for the given key components, with Studio's sentence — what a command's failed run and the preview's pre-run check both say: "scripted has no recorded answer for step 2: the tool list differs from the recording — run on live" (", or replay from an earlier step" when the source never answered it), the refusals' own suffix (studio/playground.go): one sentence per miss, before and after Run.

Example

ExampleScriptedMissReason is the same sentence for a miss predicted before Run (the preview's scripted_key_match).

package main

import (
	"fmt"

	"github.com/weftgo/weft/studio/runtime"
)

func main() {
	fmt.Println(runtime.ScriptedMissReason(0, []string{"thinking"}).Message)
}
Output:
scripted has no recorded answer for step 0: the request sets a thinking level the recording does not hold — run on live

type RegisterResponse

type RegisterResponse struct {
	RuntimeID   string `json:"runtime_id"`
	CommandsURL string `json:"commands_url"`
}

RegisterResponse answers a registration.

type Registration

type Registration struct {
	RuntimeID string `json:"runtime_id"`
	Host      string `json:"host"`
	Pid       int    `json:"pid"`
	// Cwd is the app's working directory ("" when the runtime could not
	// read it, or is older than the field): one of the inputs of its
	// stable id, and shown in GET /api/runtimes under db.path's rule.
	Cwd string `json:"cwd,omitempty"`
	// Instance tells two runtime links of one process apart (a random
	// value per link, kept across its reconnects): with host and pid it
	// is what makes a registration "the same process" — whose reconnect
	// keeps its id — rather than a second one colliding on it
	// (serveRegister). A client must send one to keep at-most-once
	// across its own restarts; one that does not is told apart by host
	// and pid alone (a restarted container reusing pid 1 would then read
	// as the old process), and Studio WARNs once.
	Instance string `json:"instance,omitempty"`
	// StudioURL is the Studio the runtime's link dialled, credentials
	// stripped ("" in-process, or a runtime older than the field): what
	// lets this Studio say where a runtime went (UX-D-01).
	StudioURL   string `json:"studio_url,omitempty"`
	Service     string `json:"service"`
	Env         string `json:"env"`
	WeftVersion string `json:"weft_version"`
	Budget      Budget `json:"budget"`
	Threads     bool   `json:"threads"`
	// Sessions says the runtime was installed with runtime.Sessions: it
	// resolves the app's own sessions by public id, so the debugger's
	// verbs on an app-owned turn of its service route to it as a
	// `session` frame (EnqueueSession) instead of being refused. A
	// runtime older than the option sends false.
	Sessions bool `json:"sessions"`
	// BreakpointsOwn says an agent the runtime exposes was built with
	// runtime.Breakpoints(): the debugger's breakpoints park tools on
	// the app's own runs of it (core.ParkSource, ADR 0030), not only on
	// the runs the runtime starts. A runtime older than the option
	// sends false.
	BreakpointsOwn bool `json:"breakpoints_own"`
	// Breakpoints is the debugger's tool set the runtime holds right
	// now (WEFT-DEVTOOLS §8.3). The set lives in the runtime's process,
	// so it outlives a Studio restart: a Studio that holds no set for
	// the runtime adopts this one (serveRegister).
	Breakpoints []string            `json:"breakpoints"`
	Agents      []AgentRegistration `json:"agents"`
	// HeartbeatMS is the cadence, in milliseconds, at which the runtime
	// re-posts a command's accepted ack while the command is in its
	// hands (waiting for a run slot, running) — its word that the
	// command is still alive. A runtime that heartbeats gets a finish
	// watch on every accepted command (serveAcks), so a lost response
	// to the accepted ack cannot leave the row accepted forever while
	// the stream stays open; one older than the heartbeat sends 0 and
	// gets the watch only when its stream ends, as before.
	HeartbeatMS int64 `json:"heartbeat_ms"`
}

Registration is the POST /api/runtime/register body.

func (Registration) Agent

func (r Registration) Agent(name string) (AgentRegistration, bool)

Agent is an agent lookup on a registration.

type RunCommand added in v0.19.0

type RunCommand struct {
	CommandStatus
	// Verb is decide, steer or cancel; CallID and Decision a decide's.
	Verb     string `json:"verb"`
	CallID   string `json:"call_id,omitempty"`
	Decision string `json:"decision,omitempty"`
	// PublicID is the command's scope (a panel token lists its own).
	PublicID string `json:"-"`
}

RunCommand is one command acting on a run — a decision on one of its parked calls (an approve frame, or a session frame's decide), a steer or a cancel of an app turn — as GET /api/runs/{id}/commands lists it (U20.2, UX-H-15): so a command lost while the run waited (the runtime went away, Studio restarted) stays marked on the run after a reload, not only on the command's own id. CommandStatus's run_id is the run the command's ack named (a decision's resumed turn).

type RuntimeServer

type RuntimeServer struct {
	// AckDeadline is how long a queued command may go unacked before
	// it is lost (§10.5: 30 s). A field so tests can tighten it.
	AckDeadline time.Duration
	// FinishDeadline is how long an accepted command may stay
	// unfinished with no word from its runtime before it is lost
	// (§10.5: 10 min): after its runtime's stream died, or — for a
	// runtime that heartbeats (Registration.HeartbeatMS) — since its
	// last accepted ack, stream open or not, the watch then being
	// max(FinishDeadline, three heartbeats) with the heartbeat taken at
	// most FinishDeadline. A field so tests can tighten it.
	FinishDeadline time.Duration
	// PingEvery is the SSE keep-alive cadence (15 s).
	PingEvery time.Duration
	// Retention is how long a terminal command (finished, rejected,
	// lost), a run's route and a disconnected runtime that still holds
	// a breakpoint set stay in memory (24 h). A disconnected runtime
	// with nothing pending goes after FinishDeadline: replicas and
	// services come and go, and each would otherwise leave an entry in
	// GET /api/runtimes forever (an app restarted in the same place
	// re-registers under its stable id and reclaims its entry). A field so tests can tighten
	// it.
	Retention time.Duration
	// WriteTimeout bounds one frame's write to the command stream
	// (30 s): a runtime that stopped reading without closing must not
	// hold the handler in Write forever. A field so tests can tighten
	// it.
	WriteTimeout time.Duration
	// contains filtered or unexported fields
}

RuntimeServer is the registry of connected runtimes and their commands: it mounts the three §10.3 routes (Mount), hands the playground its views and its enqueue (Snapshot, Registration, Enqueue, Command), and runs §10.5's lost-command timers. It is the type Server.Runtime returns once the studio side wires it (S4.1; see notes-lane-c2.md for the studio.go/routes.go lines that merge applies — this lane owns neither file).

Example

ExampleRuntimeServer mounts the runtime link's three routes — the block a Studio with Playground(true) serves — and answers one registration.

package main

import (
	"fmt"
	"net/http"
	"net/http/httptest"
	"strings"

	"github.com/weftgo/weft/studio/runtime"
)

func main() {
	rs := runtime.New()
	mux := http.NewServeMux()
	rs.Mount(mux)
	ts := httptest.NewServer(mux)
	defer ts.Close()

	reg := `{"runtime_id":"rt_demo","host":"laptop","pid":1,"service":"acme-api","env":"dev",
	         "weft_version":"v0.6.0",
	         "budget":{"max_tokens_per_experiment":200000,"max_runs_per_experiment":60},
	         "threads":false,
	         "agents":[{"name":"acme-support","manifest":"{}","models":["glm-5.3-flash"],
	                    "limits":{"max_steps":10,"parallelism":4},
	                    "side_effects":{"lookup_order":"never"},
	                    "allow":["lookup_order"]}]}`
	resp, err := http.Post(ts.URL+"/api/runtime/register", "application/json", strings.NewReader(reg))
	if err != nil {
		fmt.Println("register:", err)
		return
	}
	defer func() { _ = resp.Body.Close() }()
	fmt.Println("register:", resp.StatusCode)

	views := rs.Snapshot()
	fmt.Println("runtimes:", len(views), views[0].ID, views[0].Agents[0].Name)
}
Output:
register: 200
runtimes: 1 rt_demo acme-support

func New

func New(opts ...Option) *RuntimeServer

New builds a RuntimeServer with §10.5's timers: empty, or — WithStore — holding what the store kept from before a restart, decided as store.go says.

func (*RuntimeServer) AnyOwnRuns added in v0.15.0

func (rs *RuntimeServer) AnyOwnRuns() bool

AnyOwnRuns reports whether any connected runtime acts on the app's own runs: it registered runtime.Sessions (decisions, steer and cancel on the app's turns) or an agent built with runtime.Breakpoints() (breakpoints park the app's runs) — meta's debug_scope reads own+runtime then (plan F4).

func (*RuntimeServer) AnySessions added in v0.15.0

func (rs *RuntimeServer) AnySessions() bool

AnySessions reports whether any connected runtime registered runtime.Sessions — meta's debug_scope reads own+runtime then.

func (*RuntimeServer) BreakpointsOf

func (rs *RuntimeServer) BreakpointsOf(runtimeID string) []string

BreakpointsOf returns the runtime's stored breakpoint set.

func (*RuntimeServer) Cancel

func (rs *RuntimeServer) Cancel(runtimeID, commandID string)

Cancel sends an `event: cancel` frame for a command (the debugger rungs' verb; not a P0 route). Best effort: the runtime cancels what it started, and never the app's own runs.

func (*RuntimeServer) CancelRun added in v0.14.0

func (rs *RuntimeServer) CancelRun(runID string) (CancelTarget, error)

CancelRun asks the runtime that started runID to cancel the command holding it (an `event: cancel` frame for that command id): the runtime cancels the command's own context, and the run ends as the core ends a canceled run — its error and partial transcript in the records, its command finished failed. It asks, never kills: the frame is the whole effect, and a run that ends first simply finishes.

The command is the accepted one whose acks named the run (the experiment's own command, or the approval command a resumed run runs under). Errors: ErrUnknownRun (no runtime started the run), ErrRunFinished (its command is no longer accepted), ErrNotConnected (the runtime holds no live stream, or its feed is full — the stream is then dropped, as every enqueue drops it).

func (*RuntimeServer) Close added in v0.15.0

func (rs *RuntimeServer) Close()

Close stops the server's timers and its write-through, then drains the writer's queue (bounded at storeCloseTimeout): what was decided before Close reaches the store, and nothing after it does (a Studio closed before another opens the same database must not mark that one's commands lost from its stale timers). The memory state stays readable. Safe to call more than once.

func (*RuntimeServer) Command

func (rs *RuntimeServer) Command(id string) (CommandStatus, error)

Command returns the command's lifecycle row; ErrUnknownCommand otherwise.

func (*RuntimeServer) CommandOf

func (rs *RuntimeServer) CommandOf(id string) (Command, bool)

CommandOf returns the command as enqueued (its public id names the page a panel token is scoped to); ok is false for an unknown id.

func (*RuntimeServer) CommandsOn added in v0.19.0

func (rs *RuntimeServer) CommandsOn(runID string) []RunCommand

CommandsOn lists the commands acting on runID (RunCommand), oldest first; empty, never nil, when none is held.

Example

ExampleRuntimeServer_CommandsOn lists the commands acting on one run (GET /api/runs/{id}/commands): here a decision on a parked call of an app turn, sent to the runtime that registered runtime.Sessions and not acked yet. A row the runtime never acks turns lost after AckDeadline, and stays listed on the run.

package main

import (
	"fmt"
	"net/http"
	"net/http/httptest"
	"strings"

	"github.com/weftgo/weft/studio/runtime"
)

func main() {
	rs := runtime.New()
	mux := http.NewServeMux()
	rs.Mount(mux)
	ts := httptest.NewServer(mux)
	defer ts.Close()

	reg := `{"runtime_id":"rt_app","host":"laptop","pid":1,"service":"acme-api","sessions":true,
	         "agents":[{"name":"acme-support","manifest":"{}"}]}`
	resp, err := http.Post(ts.URL+"/api/runtime/register", "application/json", strings.NewReader(reg))
	if err != nil {
		fmt.Println("register:", err)
		return
	}
	_ = resp.Body.Close()
	stream, err := http.Get(ts.URL + "/api/runtime/commands?runtime=rt_app")
	if err != nil {
		fmt.Println("commands:", err)
		return
	}
	defer func() { _ = stream.Body.Close() }()

	if _, err := rs.EnqueueSession("rt_app", runtime.SessionCommand{Verb: runtime.VerbDecide,
		PublicID: "pub_1", SessionID: "s_1", RunID: "s_1-t1", CallID: "call_refund", Decision: "approve"}); err != nil {
		fmt.Println("enqueue:", err)
		return
	}
	for _, c := range rs.CommandsOn("s_1-t1") {
		fmt.Println(c.Verb, c.CallID, c.Decision, c.State)
	}
}
Output:
decide call_refund approve queued

func (*RuntimeServer) Connected

func (rs *RuntimeServer) Connected(runtimeID string) bool

Connected reports whether the runtime holds a live command stream.

func (*RuntimeServer) Enqueue

func (rs *RuntimeServer) Enqueue(runtimeID string, cmd Command) (Command, error)

Enqueue validates nothing (that is the playground route's job) and hands cmd to the named runtime's stream, minting the command id when the caller did not. Errors: ErrUnknownRuntime (404), ErrNotConnected (503), ErrDuplicateCommand (409), ErrInvalidCommandID (400: a caller's id outside ValidCommandID).

func (*RuntimeServer) EnqueueApproval

func (rs *RuntimeServer) EnqueueApproval(runtimeID string, d ApprovalDecision) (ApprovalDecision, error)

EnqueueApproval forwards a human decision on one parked call of a runtime-started run (WEFT-DEVTOOLS §8.2) as an `event: approve` command, minting the command id. The runtime acks it before resuming (at-most-once like every command) and reports the resumed run in its finished ack. Errors match Enqueue's.

func (*RuntimeServer) EnqueueSession added in v0.15.0

func (rs *RuntimeServer) EnqueueSession(runtimeID string, sc SessionCommand) (SessionCommand, error)

EnqueueSession hands a debugger verb on an app-owned turn to the runtime that registered runtime.Sessions (an `event: session` frame), minting the command id. The row carries sc.PublicID (a panel token polls its own command); its acks name the app's run, and never make it a run the runtime started (serveAcks). Errors match Enqueue's.

func (*RuntimeServer) Forget added in v0.19.0

func (rs *RuntimeServer) Forget(runtimeID string) error

Forget removes a runtime that is gone from the list and from the command store — its row and its breakpoint set (U20.1's "forget" of a stale or restored runtime). Its commands and run routes stay as history. ErrUnknownRuntime for an id nothing registered; ErrConnected while it holds a command stream; ErrRuntimeBusy while a queued or accepted command is addressed to it (its fate is still being decided). A runtime that registers again afterwards is listed anew, as one this Studio never saw.

Example

ExampleRuntimeServer_Forget removes a runtime that is gone — it registered once and holds no command stream — from the list (and, built WithStore, from the command store); a connected one is refused with ErrConnected.

package main

import (
	"fmt"
	"net/http"
	"net/http/httptest"
	"strings"

	"github.com/weftgo/weft/studio/runtime"
)

func main() {
	rs := runtime.New()
	mux := http.NewServeMux()
	rs.Mount(mux)
	ts := httptest.NewServer(mux)
	defer ts.Close()

	reg := `{"runtime_id":"rt_gone","host":"laptop","pid":1,"service":"acme-api",
	         "agents":[{"name":"acme-support","manifest":"{}"}]}`
	resp, err := http.Post(ts.URL+"/api/runtime/register", "application/json", strings.NewReader(reg))
	if err != nil {
		fmt.Println("register:", err)
		return
	}
	_ = resp.Body.Close()

	fmt.Println("listed:", len(rs.Snapshot()), "connected:", rs.Connected("rt_gone"))
	fmt.Println("forget:", rs.Forget("rt_gone"))
	fmt.Println("listed:", len(rs.Snapshot()))
}
Output:
listed: 1 connected: false
forget: <nil>
listed: 0

func (*RuntimeServer) HoldsInstance added in v0.19.0

func (rs *RuntimeServer) HoldsInstance(instance string) bool

HoldsInstance reports whether a runtime registered here with this link instance (Registration.Instance), connected or not: an HTTP link of this process that dialled this very server.

Example

ExampleRuntimeServer_HoldsInstance tells a link that dialled this server from one that dialled another: the registration carries the link's random instance.

package main

import (
	"fmt"
	"net/http"
	"net/http/httptest"
	"strings"

	"github.com/weftgo/weft/studio/runtime"
)

func main() {
	rs := runtime.New()
	mux := http.NewServeMux()
	rs.Mount(mux)
	ts := httptest.NewServer(mux)
	defer ts.Close()

	reg := `{"runtime_id":"rt_app","host":"laptop","pid":1,"service":"acme-api","instance":"inst_a",
	         "agents":[{"name":"acme-support","manifest":"{}"}]}`
	resp, err := http.Post(ts.URL+"/api/runtime/register", "application/json", strings.NewReader(reg))
	if err != nil {
		fmt.Println("register:", err)
		return
	}
	_ = resp.Body.Close()
	fmt.Println(rs.HoldsInstance("inst_a"), rs.HoldsInstance("inst_b"))
}
Output:
true false

func (*RuntimeServer) Manifests added in v0.11.0

func (rs *RuntimeServer) Manifests() []ManifestSource

Manifests lists the remembered registration manifests: live ones first, then the most recently registered. Live is computed now — a runtime holding its command stream registered with that manifest.

func (*RuntimeServer) Mount

func (rs *RuntimeServer) Mount(mux *http.ServeMux)

Mount registers the three runtime-link routes (§10.3) on mux. The studio server mounts them under its /api tree, so its token gate (the Studio destination's token, S4.6) applies: nothing in-process (setup A), the bearer token otherwise.

func (*RuntimeServer) MountGuarded

func (rs *RuntimeServer) MountGuarded(mux *http.ServeMux, guard func(http.Handler) http.Handler)

MountGuarded is Mount wrapping each route with guard when it is non-nil. The studio side passes its server-identity guard: the link speaks server-to-server (a runtime dials Studio with the server token, or nothing in setup A), and a panel token that passes the wall must not reach it — register overwrites any runtime's registration, the commands stream replaces its feed, and acks forge the accepted/finished state that steer and approval routing trust. None of that is public-id-shaped, so S4.6's per-public-id scoping cannot apply; the guard is the refusal.

func (*RuntimeServer) PublicOf

func (rs *RuntimeServer) PublicOf(runID string) (string, bool)

PublicOf returns the public id a runtime-started run was scoped to at enqueue. ok is false when it carried none.

func (*RuntimeServer) Registration

func (rs *RuntimeServer) Registration(runtimeID string) (Registration, bool)

Registration returns the runtime's current registration (the authoritative copy of its agents, allow-lists and caps) when it has registered; ok is false for an unknown id.

func (*RuntimeServer) RuntimeOf

func (rs *RuntimeServer) RuntimeOf(runID string) (string, bool)

RuntimeOf names the runtime that started runID (learned from its acks). ok is false for a run no runtime started — the app's own turns, viewer-only unless a runtime registered Sessions (SessionsRuntime, plan F4).

func (*RuntimeServer) SessionsRuntime added in v0.15.0

func (rs *RuntimeServer) SessionsRuntime(service string) (string, bool)

SessionsRuntime names the connected runtime of service that registered runtime.Sessions — the one an app-owned run of that service is acted on through (the newest seen when several are). ok is false when none is connected: the app's turns are then read-only. An empty service matches no runtime: a run whose records name no service cannot be told apart from another app's.

func (*RuntimeServer) SetBreakpoints

func (rs *RuntimeServer) SetBreakpoints(runtimeID string, tools []string) error

SetBreakpoints stores the runtime's breakpoint set and forwards it (an `event: breakpoints` frame): the runtime passes the named tools as core.ParkOn on every run it starts from then on. It cannot alter agents it did not build — the app's own turns are not breakable from here (PQ7), and meta says so. The set's surface reads "api" (SetBreakpointsFrom names one).

func (*RuntimeServer) SetBreakpointsFrom added in v0.20.0

func (rs *RuntimeServer) SetBreakpointsFrom(runtimeID string, tools []string, surface string) error

SetBreakpointsFrom is SetBreakpoints naming the surface the set came through (RuntimeView.BreakpointsSurface, U30.2; "" reads "api"): Studio's PUT …/breakpoints passes its body's surface.

func (*RuntimeServer) Shutdown added in v0.15.0

func (rs *RuntimeServer) Shutdown(ctx context.Context) error

Shutdown is Close with the drain bounded by ctx instead: ctx.Err() when the queue could not be written in time (what was left is dropped).

func (*RuntimeServer) Snapshot

func (rs *RuntimeServer) Snapshot() []RuntimeView

Snapshot lists the runtimes that ever registered, newest-seen first, with their liveness and agents in §10.4's shape. A runtime that registered but holds no stream stays visible until its FinishDeadline-watched commands resolve (the UI reads last_seen), then it is pruned (pruneLocked).

func (*RuntimeServer) Steer

func (rs *RuntimeServer) Steer(runtimeID, runID, message string) error

Steer forwards one user message into a runtime-started run (an `event: steer` frame): core.Steering on an ephemeral run the runtime holds, thread's Steer policy on a fork it owns. Best effort — the run may have ended between the read and the delivery.

type RuntimeView

type RuntimeView struct {
	ID   string `json:"id"`
	Host string `json:"host"`
	Pid  int    `json:"pid"`
	// Cwd is the registration's working directory — a filesystem path,
	// so Studio's route serves it under db.path's rule (absent to a
	// panel token).
	Cwd string `json:"cwd,omitempty"`
	// StudioURL is the Studio the runtime's link dialled (its
	// registration's; absent in-process or from a runtime older than
	// the field). An address of this machine's network, so Studio's
	// route serves it under cwd's rule.
	StudioURL string `json:"studio_url,omitempty"`
	// WeftVersion is the weft the runtime was built with, as it
	// registered ("" from a client that sends none): two links of one
	// process read the same (UX-E-56).
	WeftVersion    string    `json:"weft_version,omitempty"`
	Service        string    `json:"service"`
	Env            string    `json:"env"`
	ConnectedSince time.Time `json:"connected_since"`
	LastSeen       time.Time `json:"last_seen"`
	// Connected reports whether the runtime holds a live command stream
	// now. A runtime whose stream dropped stays listed (until its
	// commands resolve, pruneLocked) with Connected false; a reconnect
	// re-registers under the same id and reads true again.
	Connected bool `json:"connected"`
	// Restored marks a runtime listed from the command store after a
	// Studio restart that has not registered since: its stream is gone
	// with the old Studio (Connected false) until it reconnects. Absent
	// otherwise.
	Restored bool `json:"restored,omitempty"`
	// Sessions is the registration's: the runtime acts on the app's own
	// turns (runtime.Sessions) — approvals, steer and cancel on an
	// app-owned run of its service reach it.
	Sessions bool `json:"sessions"`
	// BreakpointsOwn is the registration's: the runtime's breakpoints
	// park tools on the app's own runs of an agent built with
	// runtime.Breakpoints() (plan F4.2).
	BreakpointsOwn bool        `json:"breakpoints_own"`
	Agents         []AgentView `json:"agents"`
	// Breakpoints is the debugger's stored tool set for this runtime
	// (WEFT-DEVTOOLS §8.3) — what PUT …/breakpoints last delivered, so a
	// reloaded UI shows the rule that is parking its runs. Empty, never
	// null.
	Breakpoints []string `json:"breakpoints"`
	// BreakpointsSetAt is when Studio last delivered the set (PUT
	// …/breakpoints, UX-A-33), kept in the command store (U30.2): a set
	// restored after a restart keeps it. Absent for a set adopted from a
	// registration, and for one an older Studio stored.
	BreakpointsSetAt *time.Time `json:"breakpoints_set_at,omitempty"`
	// BreakpointsSurface is the surface the set came through (U30.2,
	// UX-A-33's "set by <surface>"): the Studio page that sent it — the
	// PUT body's surface, "runtimes" from /runtimes — else "api" for a
	// PUT that named none. Kept and absent as BreakpointsSetAt is.
	BreakpointsSurface string `json:"breakpoints_surface,omitempty"`
	// BreakpointsBy says where a non-empty set came from (U20.1, "who
	// set them"): BreakpointsByStudio — PUT …/breakpoints through this
	// Studio (BreakpointsSetAt says when); BreakpointsByRuntime — the
	// runtime's own registration carried it (an earlier Studio set it,
	// the runtime kept it); BreakpointsByStore — restored from the
	// command store after a Studio restart. Absent for an empty set.
	BreakpointsBy string `json:"breakpoints_by,omitempty"`
}

RuntimeView is one connected runtime in GET /api/runtimes (§10.4's shape, pinned): identity, liveness, and per agent the alternate models and every tool with its side-effect class and allow flag.

type SessionCommand added in v0.15.0

type SessionCommand struct {
	CommandID string `json:"command_id"`
	Verb      string `json:"verb"` // decide | steer | cancel
	PublicID  string `json:"public_id"`
	SessionID string `json:"session_id"`
	RunID     string `json:"run_id"`
	CallID    string `json:"call_id,omitempty"`
	Decision  string `json:"decision,omitempty"`
	Reason    string `json:"reason,omitempty"`
	Content   string `json:"content,omitempty"`
	Message   string `json:"message,omitempty"`
	Actor     string `json:"actor,omitempty"`
}

SessionCommand is an `event: session` frame's data (plan F4): a debugger verb on one of the app's own turns — a run no runtime started — addressed by the session behind it: PublicID (what the app's runtime.Sessions resolver answers), SessionID (checked against the Session it answers) and RunID (the app's run). Verb decide carries CallID, Decision (approve | deny | resolve), Reason and Content as ApprovalDecision does; steer carries Message; cancel nothing more. The runtime acks it like every command — accepted before it writes the session, finished after — and refuses it, before any write, with the reason: "the app is writing this session; fork instead" when the Session it was handed cannot take the writer lease, "no session for this public id", "no running turn to steer", "cancel is not supported on an app turn".

type SourceSpec

type SourceSpec struct {
	RunID    string `json:"run_id"`
	FromStep int    `json:"from_step"`
}

SourceSpec names the run to re-run.

type SteerMessage

type SteerMessage struct {
	RunID   string `json:"run_id"`
	Message string `json:"message"`
}

SteerMessage is an `event: steer` frame's data (§8.4): a user message delivered into a runtime-started run — core.Steering on an ephemeral run, thread's Steer policy on a fork the runtime owns. The app's own turns are steered through a SessionCommand instead, on a runtime that registered Sessions (plan F4).

type ToolChoice added in v0.13.0

type ToolChoice struct {
	Mode string `json:"mode"`
	Name string `json:"name,omitempty"`
}

ToolChoice is a tool choice on the wire: mode auto | any | none | named, and the tool's name under named.

type ToolView

type ToolView struct {
	Name        string `json:"name"`
	SideEffects string `json:"side_effects"`
	Allow       bool   `json:"allow"`
	Approval    bool   `json:"approval,omitempty"`
}

ToolView is one tool of a connected runtime's agent. SideEffects is its replay class — "safe" (re-run on a replay) or "never" (never re-run: substituted, parked or, opted in, allowed) — not whether the tool has effects. Approval says the tool is built with RequireApproval (its manifest's require_approval): a gated call, so "never" beside it reads as what it is (UX-H-22).

type TranscriptEdit

type TranscriptEdit struct {
	Kind       string          `json:"kind,omitempty"`
	Step       int             `json:"step"`
	ToolResult string          `json:"tool_result,omitempty"`
	CallID     string          `json:"call_id,omitempty"`
	Content    string          `json:"content,omitempty"`
	Args       json.RawMessage `json:"args,omitempty"`
	Index      int             `json:"index,omitempty"`
}

TranscriptEdit is a D2/D3 edit of the kept prefix before the experiment re-runs from from_step (ADR 0029 §8). Kind is the discriminator — tool_result | reply | user | tool_args | insert — and absent, the fields decide as before F2 (tool_result + call_id patch a result, content rewrites a call-free reply), so every body recorded before it still validates. user rewrites a user message of step Step (Content; Index picks among the step's user messages, step 0's turn prompt first); tool_args rewrites call CallID's arguments (Args, a JSON object checked against the tool's schema); insert adds a user message (Content) at the boundary before step Step's model call, 0..from_step — where a steer delivered there would land.

Jump to

Keyboard shortcuts

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