agents

package
v1.801.441 Latest Latest
Warning

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

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

Documentation

Overview

Package agents is autonomous agents for your org: define them, run them, keep every run.

An agent is a model + a system prompt (instructions) + a set of tool names; running one executes a real chat completion through the in-process AI client (the SAME gateway path the rest of the console uses) and records the run.

Tenant isolation is the FILE: each org's records live in its own SQLite at {DataDir}/orgs/{slug}/agents.db (HIP-0302), named from the gateway-minted X-Org-Id (HIP-0026) and nothing else. One tenant cannot read, run or delete another's agents because the query never reaches the database they are in. tenancy.go is the whole of that argument and is the only file that resolves a store; the org predicate every statement still carries is what makes a mis-resolved store fail closed rather than answer.

Surface (all org-scoped; console's AgentsModule reads {agents:[...]}):

GET    /v1/agents               list agents for the org      -> {agents:[...]}
POST   /v1/agents               create an agent              -> Agent
GET    /v1/agents/:ref          agent detail + recent runs   -> AgentDetail
PATCH  /v1/agents/:ref          update an agent              -> Agent
DELETE /v1/agents/:ref          delete an agent (+ its runs)
POST   /v1/agents/:ref/run      run the agent {input}        -> RunResult
GET    /v1/agents/:ref/runs     run history                  -> {runs:[...]}

:ref is either the agent's public id (the `agent_...` handle create and list return) OR its org-unique name — resolved by Store.Resolve, so a created agent is immediately gettable and runnable by whatever create/list handed back.

The stores are per-org SQLite under deps.DataDir (Base/SQLite-only), opened through cloud.OrgStore like every other per-org subsystem. They hold definitions and run I/O only — never a secret; tool credentials live in KMS by reference.

Index

Constants

View Source
const (
	TrailerSession = "Hanzo-Session"
	TrailerTurn    = "Hanzo-Turn"

	// NotesRef is where a link is recorded for a commit that already exists.
	// Rewriting history to add a trailer would change every downstream sha and
	// break every URL already pointing at it; a note attaches the same fact
	// without touching the commit.
	NotesRef = "refs/notes/hanzo-provenance"

	// ProvenanceLogFormat is the EXACT `git log --format=` a client uses to emit
	// what ParseLinks reads. Published as a constant so the producer (the CLI
	// that ingests a transcript) and the verifier (anyone auditing our claims)
	// run the same command, and so the parser can never quietly drift from the
	// format it parses. Records are NUL-separated; within a record the first line
	// is the sha and the rest is the message body plus any note.
	ProvenanceLogFormat = "%x00%H%n%B%n%N"
)

Trailer keys. The SAME two keys are read from a commit message trailer and from a refs/notes/hanzo-provenance note body — a note is just trailer lines attached out-of-band — so one parser covers both and there is exactly one spelling of the binding anywhere in the system.

View Source
const (
	KindMessage  = "message"
	KindToolCall = "tool-call"
	KindSpawn    = "spawn"
	KindLog      = "log"
	KindStatus   = "status"
	KindControl  = "control"
)

Event kinds — the closed vocabulary of a session's ordered log.

View Source
const (
	CmdPause   = "pause"
	CmdResume  = "resume"
	CmdStop    = "stop"
	CmdMessage = "message"
)

Control commands — the closed vocabulary of remote steering.

View Source
const (
	StatusRunning = "running"
	StatusPaused  = "paused"
	StatusDone    = "done"
	StatusError   = "error"
)

Session status values. running/paused are live; done/error are terminal.

View Source
const (
	ModeOneShot     = "one-shot"
	ModeLongRunning = "long-running"
)

Execution modes. One-shot agents run only on an explicit POST; long-running agents are additionally invoked by the scheduler on their Schedule.

View Source
const (
	TargetLaptop  = "laptop"
	TargetCloud   = "cloud"
	TargetGPU     = "gpu"
	TargetCluster = "cluster"
	TargetMachine = "machine"
)

Target kinds — the closed vocabulary of dispatch destinations.

View Source
const (
	TargetOnline   = "online"
	TargetOffline  = "offline"
	TargetDraining = "draining"
)

Target status — a registered target is online until marked otherwise.

View Source
const LiveWindow = 90 * time.Second

LiveWindow bounds heartbeat freshness. A target that has heartbeated before but not within this window is reported offline no matter what its row says. Matched to the agent beat (30s) with slack so one missed beat does not flap a machine.

Variables

This section is empty.

Functions

func BillingActor

func BillingActor(org, sub string) string

BillingActor is the exported form of the actor identity a session is recorded under. The login-manager adapter (the only external caller) uses it to scope a session stop/count to the REVOKING user's own actor, so a revoke can never reach a co-tenant's sessions. It mirrors what sessions.go stamps on Session.Actor, so a stop's actor predicate matches exactly the sessions that user created.

func CloseSession

func CloseSession(ctx context.Context, org, sessionID, status string) error

CloseSession moves an org's session to a terminal state (done|error), stamping ended_at, and publishes the update. The store's monotonic-terminal rule already forbids reopening a finished run; here we only ever set a terminal status, so a double-close is a harmless no-op on an already-terminal row.

func CountActiveSessions

func CountActiveSessions(ctx context.Context, org string, m SessionMatch) (int, error)

CountActiveSessions returns how many of org's sessions matching m are live (running|paused) — the device view's "active sessions". Org-scoped; 0 when not mounted or the match is empty.

func LogSessionEvent

func LogSessionEvent(ctx context.Context, org, sessionID, kind, actor string, payload []byte) error

LogSessionEvent appends one ordered event (message|tool-call|spawn|log|status| control) to an org's session and fans it out live. The (org, id) pair is re-resolved so a caller can only write to a session THIS org owns; kind is validated against the closed vocabulary and payload is size-bounded + must be well-formed JSON (nil payload is allowed for a bare marker). actor falls back to the org.

func Mount

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

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

func OfferRoutedRun

func OfferRoutedRun(run RoutedRun) *offer

OfferRoutedRun is the exported seam the coding delivery activity uses to place a run into the live rendezvous. Kept here (agents owns targets + sessions) so the machine-facing HTTP surface and the durable activity share ONE mailbox.

func OpenSession

func OpenSession(ctx context.Context, org, actor, agent, title string) (string, error)

OpenSession registers a LIVE root session for org attributed to actor (an "org/sub" identity or a bare label) with the given agent label + title, and returns its id. The session is born running (not terminal like openRunSession's completed one-shot) so a long job streams status/log/tool-call events into it until CloseSession moves it to a terminal state. Best-effort live fan-out (publish) rides the bus; the store row is the truth.

func OpenSessionOn

func OpenSessionOn(ctx context.Context, org, actor, agent, title, target string) (string, error)

OpenSessionOn is OpenSession with the run's dispatch TARGET recorded, so mission-control shows a routed run on the machine it was sent to (session.target == the target id) exactly as a locally-linked run shows its host. The target is re-resolved org-scoped and MUST belong to this org — a session can never claim to run on another tenant's machine (the same fail-closed rule sessionContext enforces on the HTTP register path). An empty target falls back to OpenSession.

func SeedPersonalities

func SeedPersonalities(ctx context.Context, org string) (int, error)

SeedPersonalities ensures the built-in crew exists for org. Idempotent: an already-present persona (UNIQUE org+name) is left untouched, so it is safe to call on every org first-touch (a new Team workspace, say). Returns the number newly created.

It needs a model to attach — the deployment's configured default. With no default model, seeding is a NO-OP (0, nil): an org gets its crew the moment the binary has a model to run them on, never a half-created persona that can't run. A subsystem that is not mounted also no-ops rather than erroring, so a caller on the login path can call it best-effort without ever blocking a human.

func Shutdown

func Shutdown(ctx context.Context) error

Shutdown stops the scheduler (draining in-flight runs, bounded by ctx) and closes the agents store. Idempotent — safe to call when nothing is mounted.

func StopSessions

func StopSessions(ctx context.Context, org string, m SessionMatch) (int, error)

StopSessions closes every RUNNING|PAUSED session of org matching m — recording a control "stop" event on each and transitioning it to a terminal state — and returns how many it stopped. It is the action a login-out (link revoke) takes so the sessions that ran under a revoked account/device are torn down. Org AND Actor scope it: the caller passes their own actor (org/user), so a revoke can only ever stop the caller's OWN sessions — never a co-tenant's, never an org's every session — even though m's Host/Provider/Account come from an attacker-controllable link row. A match with no actor stops nothing (fail-closed). Not-mounted → (0, nil), so a revoke tolerates a deployment with no session plane.

func TargetDispatchable

func TargetDispatchable(ctx context.Context, org, targetID string) error

TargetDispatchable is the exported gate the coding dispatcher injects (it never imports the store directly). Returns nil when a run may be routed to (org, targetID), else a descriptive error.

Types

type Agent

type Agent struct {
	ID               string
	Org              string
	Name             string
	Model            string
	Instructions     string
	Description      string
	Tools            []string
	Status           string
	ExecutionMode    string
	Schedule         string
	ComputeRef       string
	ServiceAccountID string
	CreatedAt        int64
	UpdatedAt        int64
}

Agent is the org-scoped definition of an autonomous worker: a model, a system prompt (instructions), and a set of tool names it may call. It lives in its org's own database (see Store), and never stores a secret — tool credentials live in KMS and are referenced by name at run time.

The bot-lifecycle fields promote an agent from a one-shot callable into a long-running bot (per hanzo-agent-bot-architecture: "Bot = Agent + compute + long-running"):

  • ExecutionMode: "one-shot" (default; runs only when POSTed) or "long-running" (the scheduler invokes it on Schedule).
  • Schedule: a 5-field cron expression; required when long-running, ignored otherwise. The scheduler evaluates it once a minute.
  • ComputeRef: an optional visor machine id the bot is bound to. It is an opaque reference here; binding/lifecycle is owned elsewhere.
  • ServiceAccountID: an optional IAM agent service-account (<org>-<agent>). When set it is the Actor recorded on scheduled-run billing so an autonomous run is attributable to a principal, not just the org.

func ListForOrg

func ListForOrg(ctx context.Context, org string) ([]Agent, error)

ListForOrg returns the org's agents from the in-process store — the ONE exported seam other in-process subsystems use to read the canonical agent registry WITHOUT an HTTP hop back through the gateway.

It is the decompleced replacement for the old bots-as-members path, which enumerated agents over HTTP (/v1/agents with a forwarded bearer) and broke when HANZO_API_KEY was rejected (bot_members=0). clients/team calls this directly to project each agent as a workspace Employee.

ISOLATION: org is the ONLY tenant key and is used VERBATIM (Store.List filters WHERE org=?), so a caller for org A can never enumerate org B's agents. The caller MUST pass an org it already validated (principal.Org / a verified token claim), never a raw client header. Fails closed (nil, error) when the agents subsystem is not mounted or the org is empty/oversized.

type Event

type Event struct {
	ID        string
	SessionID string
	Org       string
	Seq       int64
	Kind      string // message|tool-call|spawn|log|status|control
	Actor     string
	Payload   string // opaque JSON blob (validated well-formed, size-bounded)
	CreatedAt int64
}

Event is one entry in a session's ordered log: a model message, a tool call, a subagent spawn, a free log line, a status change, or a control command the running surface consumes. Seq is monotonic PER SESSION so a subscriber can resume from its last-seen point; Org is denormalised so every read stays org-scoped without a join back to the session row.

type GPU

type GPU struct {
	Vendor string `json:"vendor,omitempty"` // nvidia | amd | apple | intel | ...
	Model  string `json:"model,omitempty"`  // "GB10", "8060S", "RTX 4090"
	Memory int64  `json:"memory,omitempty"` // VRAM bytes, 0 = unknown
}

GPU is one accelerator on a machine.

type Link struct {
	Commit  string `json:"commit"`
	Session string `json:"session"`
	Turn    int64  `json:"turn"`
}

Link is one commit⇄turn binding as GIT states it. It is derived, never stored: ParseLinks is a pure function of `git log` output.

func ParseLinks(gitLog string) []Link

ParseLinks reads `git log --format=ProvenanceLogFormat` output and returns one Link per commit that carries BOTH keys. A commit with neither (ordinary work), with only one (a half-written trailer), or with an unparseable turn is skipped — a link asserts a specific turn produced a specific commit, and a partial record cannot assert that. Pure: no exec, no I/O, safe to call concurrently.

type Metrics

type Metrics struct {
	Load1   float64 `json:"load1,omitempty"`
	Load5   float64 `json:"load5,omitempty"`
	Load15  float64 `json:"load15,omitempty"`
	MemUsed int64   `json:"memUsed,omitempty"` // bytes
	MemFree int64   `json:"memFree,omitempty"` // bytes
	GPUUtil float64 `json:"gpuUtil,omitempty"` // 0..1 aggregate utilization
	At      int64   `json:"at,omitempty"`      // unix seconds, server-stamped
}

Metrics is a machine's live state from the last heartbeat.

func (Metrics) IsZero

func (m Metrics) IsZero() bool

IsZero reports an all-empty metrics sample.

func (Metrics) Sanitize

func (m Metrics) Sanitize() Metrics

Sanitize coerces a metrics sample into a safe, finite range. It does NOT set At — the server stamps that so a client can never backdate or forge the staleness clock.

type Need added in v1.801.425

type Need struct {
	GPUs   int    `json:"gpus,omitempty"`   // accelerators required
	VRAM   int64  `json:"vram,omitempty"`   // bytes each accelerator must address
	CPUs   int    `json:"cpus,omitempty"`   // logical cores
	Memory int64  `json:"memory,omitempty"` // host RAM bytes
	OS     string `json:"os,omitempty"`     // linux | darwin | windows
	Arch   string `json:"arch,omitempty"`   // amd64 | arm64 | ...
}

Need is what a job requires OF a machine, written in the SAME vocabulary a machine advertises. It is the other half of Spec: Spec says what a machine has, Need says what a job wants, and Satisfies is the ONE place the two meet.

THERE IS NO VENDOR FIELD, AND THAT IS THE POINT. `resourcesPerNode.limits. "nvidia.com/gpu"` is not a requirement, it is one vendor's name for a requirement — baking it into the scheduler contract is what made a GPU job unroutable to an AMD or Apple machine that could have run it. A job needs ACCELERATORS with enough memory; which vendor satisfies that is the machine's business, and hanzo-kernel lowers one kernel source to CUDA/ROCm/Vulkan/Metal precisely so the job never has to care. Re-adding a vendor here would reintroduce the hardcode as a value, so it stays out: a requirement no advertised capability can express is not a requirement.

The zero Need is "anything will do" — every field is a floor that only constrains when set, so an unrelated caller is never forced to describe a machine it does not care about.

func (Need) IsZero added in v1.801.425

func (n Need) IsZero() bool

IsZero reports a Need that constrains nothing.

type RoutedResult

type RoutedResult struct {
	OK        bool   `json:"ok"`
	Changed   bool   `json:"changed"`
	Branch    string `json:"branch,omitempty"`
	CommitSha string `json:"commitSha,omitempty"`
	Diffstat  string `json:"diffstat,omitempty"`
	Error     string `json:"error,omitempty"`
}

RoutedResult is a routed run's terminal outcome, reported by the machine and returned to the durable activity so the workflow completes.

type RoutedRun

type RoutedRun struct {
	Org            string `json:"org"`
	TargetID       string `json:"targetId"`
	SessionID      string `json:"sessionId"` // the live session opened at dispatch; the machine streams into it
	Repo           string `json:"repo"`
	Project        string `json:"project,omitempty"`
	Base           string `json:"base,omitempty"`
	Branch         string `json:"branch"`
	Prompt         string `json:"prompt"`
	CloneURL       string `json:"cloneUrl"`
	TimeoutSeconds int    `json:"timeoutSeconds,omitempty"`
	// Actor + AgentRef are CLOUD-SIDE attribution for the completion path (session
	// close + PR assignee). They are NOT part of routedRunView, so they never cross
	// to the executing machine — the machine needs neither.
	Actor    string `json:"actor,omitempty"`
	AgentRef string `json:"agentRef,omitempty"`
}

RoutedRun is the NON-SECRET spec of one coding run dispatched to a target. It carries no credential by design: the executing machine authenticates git + model routing with its OWN already-held credentials (the same ones `hanzo code` uses), so no secret ever enters the durable store or crosses to the machine in the claim response. Everything here is safe to persist in the tasks engine.

type Run

type Run struct {
	ID         string
	Org        string
	AgentName  string
	Status     string
	Model      string
	Input      string
	Output     string
	Error      string
	DurationMs int64
	CreatedAt  int64
}

Run is one execution of an agent: the input, the produced output (or error), which model served it, and how long it took. Real history — every row is a call that actually happened.

func RunOnBehalf

func RunOnBehalf(ctx context.Context, org, userSub, ref, input string) (Run, error)

RunOnBehalf runs agent `ref` for `org` ON BEHALF OF `userSub`, IN-PROCESS — no gateway hop, no Cloudflare/IPv6 exposure. It is the clean in-process twin of the HTTP s.run handler: the CALLER (e.g. the Slack integrations bridge) has ALREADY authenticated org+userSub server-side, so this entry takes them DIRECTLY and never reads an HTTP principal / JWT / zip.Ctx. It resolves the agent org-scoped, runs it through the SAME runAgent → executeRun → meter path as s.run (one run path: one balance gate, one debit, one recorded run, one live session), and bills billingActor(org, userSub) against ORG's ledger.

ISOLATION: org is the ONLY tenant key. Store.Resolve is org-scoped, so a caller for org A can never resolve, run, or bill against org B's agent — exactly the property the HTTP handler relies on the gateway-minted X-Org-Id for.

A non-nil error means NO run happened: not mounted, invalid org, oversized input, inference not configured, agent-not-found (errNotFound), or a balance-gate denial (out-of-funds / commerce-unknown). A run that executed but whose model failed returns a recorded error-status Run and a nil error.

type Session

type Session struct {
	ID        string
	Org       string
	Agent     string // agent name / type label (need not be a cloud Agent row)
	Actor     string // the principal that started it (validated user, or a bound SA)
	Status    string // running|paused|done|error
	ParentID  string // "" for a root (the outer agent)
	RootID    string // the tree key; == ID for a root
	Title     string
	StartedAt int64
	EndedAt   int64 // 0 until a terminal status is reached
	CreatedAt int64
	UpdatedAt int64

	// TaskWorkflowID / TaskRunID link this session to the hanzoai/tasks durable
	// workflow that actually EXECUTES it. This registry is the view/control/stream
	// layer; durable execution (retries, resumability, scheduling) is owned by
	// hanzoai/tasks — NOT by a bespoke scheduler here. A root session maps to a
	// tasks workflow (ExecuteWorkflow); a subagent maps to a child workflow keyed
	// by the same RootID. When these are set, control (pause/resume/stop/message)
	// forwards to the tasks Signal/Cancel API (see State.tasks). Empty = a surface
	// that consumes control from the event stream instead (today's @hanzo/dev).
	TaskWorkflowID string
	TaskRunID      string

	// Execution context — WHERE this session runs. All optional (a surface that
	// doesn't know sets ""), surfaced by mission-control so a card shows the
	// machine/repo/cwd it runs on and the devices view maps "which sessions run
	// where". Host is the machine label; Repo/Cwd are the code context; Target is a
	// registered run-target id (the #48 dispatch association — resolved same-org at
	// register/patch so it never points across tenants). Truth the SURFACE reports.
	Host   string
	Cwd    string
	Repo   string
	Target string

	// Terminal is where this session's live terminal can be WATCHED — the URL the
	// machine published for it (zrok gives one without opening a port). Optional:
	// a session that publishes nothing is still a session, it just cannot be
	// watched. It is a URL rather than a stream because the bytes belong to the
	// machine running the shell; cloud holds the address, never the connection.
	Terminal string

	// Provider/Account tag a session with the linked AI account it ran under (the
	// login-manager tie-in): which provider (claude|codex|hanzo|…) and which
	// subscription/api account served this run. Optional (a surface that doesn't
	// know sets ""), surfaced so the cockpit shows which linked account served
	// the run and so a login-out (link revoke) can stop the sessions that used it.
	Provider string
	Account  string

	// Project / Published are the READABLE BUILD (provenance.go): which product
	// this session built, and its author's decision to let the world read the
	// story. Two columns, because "the build of project P" is just the sessions
	// tagged with P — a build log is not a second kind of thing to store.
	// Published only ever widens READ access to a session that already exists;
	// it grants nothing else, and an unpublished session is invisible to the
	// public route no matter who asks.
	Project   string
	Published bool
}

A live agent-session is a running invocation — a cloud agent run, a bot loop, or a @hanzo/dev CLI run spawning subagents. The SUBAGENT TREE is sessions linked by ParentID: the outer agent is the root (ParentID==""), each spawned subagent is a child, and RootID is the tree key every node in one flow shares. It is the first-class, streamable form of the blue/red/cto fan-out tree.

A session is NOT foreign-keyed to an agents row: an external surface (the @hanzo/dev CLI) registers a session whose Agent is just a label, not a cloud Agent definition. Tenancy is the org's own file, exactly like agents/runs.

type SessionFilter

type SessionFilter struct {
	Root      string
	Parent    string
	Status    string
	Project   string
	Published bool
	Limit     int
}

SessionFilter selects a slice of an org's sessions. The fields are AND-ed; a zero field is "any". Scope picks the structural axis:

  • Root set -> every session in that tree (root_id == Root).
  • Parent set -> the direct children of Parent (parent_id == Parent).
  • neither -> roots only (parent_id == ”), the outer-agent view.

Project narrows to the sessions that built one product; it is orthogonal to the structural axis, so `?project=x` alone lists that build's ROOTS (the default parent_id==” scope) and pairs with Root to walk its subagents. Published additionally requires the author's publish flag — the predicate the PUBLIC build route runs, so an unpublished session cannot be reached anonymously.

type SessionMatch

type SessionMatch struct {
	Actor    string
	Host     string
	Provider string
	Account  string
}

SessionMatch selects live (running|paused) sessions to stop or count. Actor (the owning subject, org/user) is MANDATORY and always ANDed, so a match can only ever affect the caller's OWN sessions — never a co-tenant's. Host/Provider/Account are optional narrowing WITHIN the actor's sessions (empty = any of the actor's). A match with no actor selects NOTHING (fail-closed), so a login-out can never sweep another user's — or an org's every — session, even when Host/Provider/Account are attacker-set at link upsert.

type Spec

type Spec struct {
	OS     string `json:"os,omitempty"`     // linux | darwin | windows
	Arch   string `json:"arch,omitempty"`   // amd64 | arm64 | ...
	CPUs   int    `json:"cpus,omitempty"`   // logical cores
	Memory int64  `json:"memory,omitempty"` // total RAM, bytes
	GPUs   []GPU  `json:"gpus,omitempty"`
}

Spec is a machine's static capability.

func (Spec) IsZero

func (s Spec) IsZero() bool

IsZero reports an all-empty spec (nothing worth storing).

func (Spec) Sanitize

func (s Spec) Sanitize() Spec

Sanitize bounds every field so a target row stays small and well-formed no matter what a client sends: strings trimmed + length-capped, counts/sizes non-negative and clamped, GPU list truncated, floats coerced finite. It is total (never errors) so the write path can always proceed with a safe value.

func (Spec) Satisfies added in v1.801.425

func (s Spec) Satisfies(n Need) bool

Satisfies reports whether this machine's advertised capability meets a job's Need. It is a pure function of two values — no clock, no store, no vendor table — so the dispatch gate, a scheduler and a UI preview all get the same answer from the same rule, and a test can state a fleet as data.

UNKNOWN IS NOT ENOUGH. A machine that advertises VRAM 0 does not satisfy a VRAM floor: 0 means "the probe could not tell", and admitting it would route a 70B job to a machine that cannot hold it. This is deliberately fail-closed, and it is why the probe reporting truthful accelerator memory matters — on a unified-memory machine (Apple Silicon, an NVIDIA GB10, an AMD APU) nvidia-smi/system_profiler/lspci report no discrete VRAM, so such a box advertises 0 and is refused by any VRAM floor until it advertises the memory its accelerator can actually address.

type Store

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

Store is ONE ORG's agents database — the file at {DataDir}/orgs/{slug}/agents.db that cloud.OrgStore opens and caches (HIP-0302 physical org isolation), holding that org's agents, runs, sessions, events, targets and claim keys.

Isolation is now the FILE. Every method still takes and still applies the org it is given, and that is deliberate: the predicate costs nothing next to the file it already runs in, and it is what makes a mis-resolved store fail closed (an empty read) instead of serving a neighbour's rows. The file is the boundary; the column is the proof that the boundary held.

func (*Store) AppendEvent

func (s *Store) AppendEvent(ctx context.Context, e Event) (Event, error)

AppendEvent inserts one event, allocating the next per-session Seq. The org's file runs on a single connection (cloud.OrgDB sets MaxOpenConns(1)) so the read-then-write of the max seq is serialised WITHIN the org, and the UNIQUE(session_id,seq) index is the final backstop. Appends in different orgs no longer queue behind each other, because they are different files. The session's updated_at is bumped in the SAME transaction so "last activity" stays truthful. Returns the persisted event (with Seq/CreatedAt) for streaming.

func (*Store) ClaimKeyHash

func (s *Store) ClaimKeyHash(ctx context.Context, org, targetID string) (hash string, servingAt int64, err error)

ClaimKeyHash returns a target's stored hash + last serving stamp, or errNoClaimKey when none was ever minted.

func (*Store) Close

func (s *Store) Close() error

func (*Store) CountChildren

func (s *Store) CountChildren(ctx context.Context, org, id string) (int, error)

CountChildren returns how many DIRECT children a session has (its fan-out).

func (*Store) CountEvents

func (s *Store) CountEvents(ctx context.Context, org, sessionID string) (int, error)

CountEvents returns how many events a session has (the list rollup).

func (*Store) CountLongRunning

func (s *Store) CountLongRunning(ctx context.Context, org string) (int, error)

CountLongRunning returns how many scheduled long-running agents an org has — used to cap an org's scheduler footprint at create time.

func (*Store) CountRuns

func (s *Store) CountRuns(ctx context.Context, org, agent string) (int, error)

CountRuns returns how many runs an org's agent has (for the list rollup).

func (*Store) Create

func (s *Store) Create(ctx context.Context, a Agent) error

Create inserts one agent. A UNIQUE(org,name) violation surfaces as errConflict.

func (*Store) CreateSession

func (s *Store) CreateSession(ctx context.Context, x Session) error

CreateSession inserts one session. When ParentID is set it MUST reference an existing session IN THE SAME ORG — the caller resolves it via GetSession first so a cross-tenant or dangling parent can never link a tree. RootID is derived by the caller (parent's root, or self for a root); this method persists what it is given after a final same-org sanity check on the parent.

func (*Store) CreateTarget

func (s *Store) CreateTarget(ctx context.Context, t Target) error

CreateTarget inserts one target. The id is caller-generated (genID("tgt")).

func (*Store) Delete

func (s *Store) Delete(ctx context.Context, org, name string) (bool, error)

Delete removes an agent and its run history. Reports whether a row went.

func (*Store) DeleteTarget

func (s *Store) DeleteTarget(ctx context.Context, org, id string) (bool, error)

DeleteTarget removes an org's target. Sessions keep their recorded target id (a historical fact); a detached target simply stops appearing in the registry.

func (*Store) EventCountsByRoot

func (s *Store) EventCountsByRoot(ctx context.Context, org, root string) (map[string]int, error)

EventCountsByRoot returns per-session event counts for EVERY session in one org's tree (root_id == root) in a SINGLE grouped query — so materialising a tree of N nodes with real per-node event counts costs one round trip, not N, and never hits SQLite's bound-parameter limit (the join scopes by root_id, not an IN list of ids).

func (*Store) Get

func (s *Store) Get(ctx context.Context, org, name string) (Agent, error)

Get returns the agent for the exact (org,name) or errNotFound. It is the precise name primitive; path-addressed handlers use Resolve (id-or-name).

func (*Store) GetLinkableTargetByHost

func (s *Store) GetLinkableTargetByHost(ctx context.Context, org, host, owner string) (Target, error)

GetLinkableTargetByHost returns the target for (org,host) that the caller `owner` may re-link — its OWN row, else an UNOWNED (pre-migration) row it may adopt — preferring the exact-owner match, newest first. A row owned by a DIFFERENT principal is NEVER returned, so a re-link can never clobber another member's machine: the caller gets its own row or (falling through in registerTarget) a fresh one. errTargetNotFound when nothing linkable exists.

func (*Store) GetSession

func (s *Store) GetSession(ctx context.Context, org, id string) (Session, error)

GetSession returns the (org,id) session or errSessionNotFound. The org is part of the key so one tenant can never resolve another's session id.

func (*Store) GetTarget

func (s *Store) GetTarget(ctx context.Context, org, id string) (Target, error)

GetTarget returns the (org,id) target or errTargetNotFound. Org is part of the key so one tenant can never resolve another's target id.

func (*Store) GetTargetByHost

func (s *Store) GetTargetByHost(ctx context.Context, org, host string) (Target, error)

GetTargetByHost returns an org's target reporting the given host, or errTargetNotFound. It is how a re-link of the SAME machine finds its existing target (idempotent register) instead of creating a duplicate. Org-scoped: a host string can never resolve another tenant's target. Newest wins if a host was ever double-listed.

func (*Store) InsertRun

func (s *Store) InsertRun(ctx context.Context, r Run) error

InsertRun records one agent execution.

func (*Store) LastEvent

func (s *Store) LastEvent(ctx context.Context, org, sessionID string) (Event, bool, error)

LastEvent returns a session's most recent event (highest seq) — the one-line "last activity" a mission-control card shows in the list without fetching full detail. ok=false when the session has no events yet. Org-scoped like every read.

func (*Store) List

func (s *Store) List(ctx context.Context, org string) ([]Agent, error)

List returns every agent for org, most-recently-updated first.

func (*Store) ListControlAfter

func (s *Store) ListControlAfter(ctx context.Context, org, sessionID string, since int64, limit int) ([]Event, error)

ListControlAfter returns a session's KindControl events with seq > since, oldest first — the durable steering queue a running surface drains. Mirrors ListEvents but filters to control so a chatty session's message/log/tool-call events never dilute a poll.

func (*Store) ListEvents

func (s *Store) ListEvents(ctx context.Context, org, sessionID string, since int64, limit int) ([]Event, error)

ListEvents returns a session's events in Seq order (optionally only those with Seq > since, so a subscriber resumes exactly where it dropped), capped.

func (*Store) ListLongRunning

func (s *Store) ListLongRunning(ctx context.Context) ([]Agent, error)

ListLongRunning returns every agent across ALL orgs whose execution_mode is long-running and that carries a non-empty schedule — the scheduler's work set. It is the ONE cross-org query in this store; the scheduler is a trusted in-process subsystem (not a tenant request), and each returned agent carries its own Org so every downstream action (run, gate, meter) stays scoped to the agent's own tenant.

func (*Store) ListPublishedBuilds

func (s *Store) ListPublishedBuilds(ctx context.Context, limit int) ([]Session, error)

ListPublishedBuilds returns every published build ACROSS ORGS, newest first. It is the one query in this file that is deliberately not org-scoped, because it answers a public question — "which builds may anyone read?" — and its WHERE clause is the publish flag itself. A row can only appear here because its own author set published=1, so cross-org visibility is the author's grant, not a missing tenant predicate. Roots only: a build's story is its outer session.

func (*Store) ListRuns

func (s *Store) ListRuns(ctx context.Context, org, agent string, limit int) ([]Run, error)

ListRuns returns the run history for (org,agent), newest first, capped.

func (*Store) ListSessions

func (s *Store) ListSessions(ctx context.Context, org string, f SessionFilter) ([]Session, error)

ListSessions returns an org's sessions per filter, newest first, capped.

func (*Store) ListTargets

func (s *Store) ListTargets(ctx context.Context, org string) ([]Target, error)

ListTargets returns an org's targets, newest first.

func (*Store) ListTree

func (s *Store) ListTree(ctx context.Context, org, root string, cap int) ([]Session, error)

ListTree returns EVERY session in one org's tree (root_id == root), oldest first so a caller can assemble parent→child in a single pass. Capped so a pathological tree can't produce an unbounded response.

func (*Store) Resolve

func (s *Store) Resolve(ctx context.Context, org, ref string) (Agent, error)

Resolve returns the agent identified by ref within org, matching either its public id (the `agent_...` handle create and list hand back) OR its org-unique name. This is the ONE lookup every path-addressed handler (get/update/delete/ run/runs) uses, so a just-created agent is immediately addressable by exactly the identifier create/list returned — no id-vs-name split. If a ref somehow equals one agent's id and another's name, the id match wins (the stable public handle is authoritative). Tenancy is the org filter, so a ref belonging to another tenant is errNotFound — fail-closed, never cross-org.

func (*Store) RunsSince

func (s *Store) RunsSince(ctx context.Context, org string, since int64, limit int) ([]Run, error)

RunsSince returns the org's runs across ALL agents with created_at >= since, newest first, capped. It powers the org-wide surfaces: the recent-activity feed (since=0 → the newest runs regardless of age) and the invocation histogram (since=windowStart → every run in the window, order-independent for bucketing). since<=0 means "no lower bound". Tenancy is the org column, so a caller never sees another org's runs. Every row is a real recorded execution.

func (*Store) SessionLoad

func (s *Store) SessionLoad(ctx context.Context, org, id, host string) (TargetLoad, error)

SessionLoad returns how many of an org's sessions are mapped to a target: those explicitly dispatched to it (target == id) OR reporting its host (host == host, when the target has a host). One exact query (no double count) per target — the list is small so the per-row cost matches the sessions list's own rollups.

func (*Store) StampServing

func (s *Store) StampServing(ctx context.Context, org, targetID string, now int64) error

StampServing records that a target's runner polled at now (its liveness heartbeat). Best-effort by the caller; a missing row is a no-op.

func (*Store) TargetDispatchable

func (s *Store) TargetDispatchable(ctx context.Context, org, targetID string) error

TargetDispatchable is the DRY liveness gate, used at dispatch (fail closed before enqueue) AND re-checked at claim. A run is dispatchable only to a target that (a) exists in this org, (b) is online, and (c) has a live runner — a claim poll within servingTTL. Any failure is an explicit error the dispatcher renders honestly; it NEVER falls back to running elsewhere.

func (*Store) Update

func (s *Store) Update(ctx context.Context, a Agent) error

Update overwrites the mutable fields of an existing agent.

func (*Store) UpdateSession

func (s *Store) UpdateSession(ctx context.Context, x Session) error

UpdateSession persists the mutable fields of an existing (org,id) session. Scoped by org so a cross-tenant id can never mutate another's session.

Every column the patch can set has to be listed here. A field added to patchSessionIn and forgotten in this statement accepts the request, answers 200 with the new value in the response body, and persists nothing — a success that did nothing, which is the hardest shape of bug to see.

func (*Store) UpdateTarget

func (s *Store) UpdateTarget(ctx context.Context, t Target) error

UpdateTarget persists mutable fields for an existing (org,id) target. Scoped by org so a cross-tenant id can never mutate another's target. owner is persisted too so a relink can BIND a previously-unowned row (registerTarget) and a patch preserves the owner it read; no client-facing patch field sets owner, so it never moves by mutation.

func (*Store) UpsertClaimKeyHash

func (s *Store) UpsertClaimKeyHash(ctx context.Context, org, targetID, hash string, now int64) error

UpsertClaimKeyHash stores (or rotates) a target's claim-key hash. serving_at is reset to 0 on a fresh mint — the daemon proves liveness by its first poll.

type Target

type Target struct {
	ID        string
	Org       string
	Owner     string // the VALIDATED principal (c.User()) that registered this machine; "" for a pre-migration row
	Label     string
	Kind      string // laptop | cloud | gpu | cluster | machine
	Status    string // online | offline | draining
	Capacity  string // free-form ("8 vCPU / 32G", "1× GB10") — human summary
	Host      string // hostname sessions on this machine report (maps sessions -> target)
	Spec      Spec   // static capability
	Metrics   Metrics
	MetricsAt int64
	CreatedAt int64
	UpdatedAt int64
}

Target is a registered agent run-target. Owned by one org. Spec is its static capability (os/arch/cpus/memory/gpus) and Metrics its last live heartbeat (loadavg/memory/gpu-util); MetricsAt is the unix second that heartbeat was recorded (0 = never). See targetspec.go for the value plane.

func ResolveTarget

func ResolveTarget(ctx context.Context, org, ref string) (Target, error)

ResolveTarget resolves a human's target REFERENCE — a target id or its friendly label (the hostname the CLI registers) — to the org's target, org-scoped and fail-closed. It is the ONE way a trigger surface (the Slack `code: <repo> on <target>` grammar, a console picker) turns "on evo" into a target id without leaking another tenant's inventory: an id or label that resolves to no target in THIS org returns errTargetNotFound, never another org's machine.

Precedence: an exact id match wins (ids are unambiguous), else an exact, case-folded label match (newest first, so a re-registered machine's live row is preferred). A reference that matches neither is not found — the caller renders an honest error and NEVER falls back to a local run.

func TargetsForOrg

func TargetsForOrg(ctx context.Context, org string) ([]Target, error)

TargetsForOrg returns the org's registered run-targets from the in-process store, newest first. Fails closed when the subsystem is not mounted or the org is empty/oversized.

func (Target) EffectiveStatus

func (t Target) EffectiveStatus(now time.Time) string

EffectiveStatus is the ONE liveness answer every reader uses — the views here, the fleet board's agent fold, and the dispatch gate. The stored status records operator INTENT ("I am draining this box"); liveness is a FACT the heartbeat decides, and the fact wins. Without this a worker that died — or whose host was simply powered off — stays "online" forever, because nothing ever writes the row again to say otherwise. That is how the fleet board came to show two GPUs online that had last beaten five and nine days earlier.

A target that has never heartbeated (MetricsAt == 0) keeps its stored status: it is a hand-registered destination, not a beating agent, and has no fact to check.

type TargetLoad

type TargetLoad struct {
	Sessions int // total sessions mapped to the target
	Running  int // of those, how many are currently running
}

TargetLoad is the live session load on a target.

func LoadOn

func LoadOn(ctx context.Context, org, id, host string) (TargetLoad, error)

LoadOn returns the live session load on one of the org's targets — the same (target id OR host) mapping the HTTP views use, so the board and /v1/agents/ targets can never disagree about what is running where.

type TaskController

type TaskController interface {
	// Signal forwards a cooperative control signal (pause/resume/message) to the
	// durable workflow backing a session. name is the signal name; payload is the
	// opaque signal argument (e.g. a steer message), nil when there is none.
	Signal(ctx context.Context, workflowID, runID, name string, payload []byte) error
	// Cancel gracefully cancels the durable workflow backing a session (a stop).
	Cancel(ctx context.Context, workflowID, runID, reason string) error
	// Enabled reports whether a real tasks backend is wired. When false the
	// control endpoints record the intent and skip the forward (honest degrade,
	// same pattern as deps.AI).
	Enabled() bool
}

TaskController is the seam to the hanzoai/tasks durable-execution engine — the ONE canonical engine for durable/retriable/scheduled agent work. This sessions surface is the REGISTRY + control + ZAP-stream VIEW layer; it deliberately owns NO scheduler, ticker, or lease. When a session is backed by a tasks workflow (Session.TaskWorkflowID set), a control command forwards through this seam to the engine's signal/cancel API instead of only being recorded.

The method set mirrors github.com/hanzoai/tasks/pkg/sdk/client.Client exactly, so the live adapter is a thin wrapper (Signal→Client.SignalWorkflow, Cancel→Client.CancelWorkflow) with no impedance mismatch:

SignalWorkflow(ctx, workflowID, runID, signalName string, arg any) error
CancelWorkflow(ctx, workflowID, runID string) error

TASKS PLUG-IN POINT. The live controller is wired in Mount from a dialed tasks client (client.Dial(TASKS_URL)); until the hanzoai/tasks native engine lands (today its workflow opcodes return 501 by design — "the shape is in place so callers depend on the API while the engine lands behind it"), the default is the disabled controller: control is still durably RECORDED as a session event for stream-consuming surfaces, and the forward is a clean no-op.

Jump to

Keyboard shortcuts

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