agents

package
v1.786.92 Latest Latest
Warning

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

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

Documentation

Overview

Package agents mounts the Hanzo Cloud /v1/agents surface: per-org autonomous agent definitions and their runs. 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 gateway-minted X-Org-Id (HIP-0026) enforced as the org column on every query, so one tenant can never read, run, or delete another's agents.

Surface (all org-scoped; console2'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 store is SQLite in deps.DataDir (Base/SQLite-only). It holds definitions and run I/O only — never a secret; tool credentials live in KMS by reference.

Index

Constants

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.

Variables

This section is empty.

Functions

func Mount

func Mount(app *zip.App, deps cloud.Deps) error

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

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.

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. Tenant isolation is the org column, enforced on every query. It 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.

type Event added in v1.786.32

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 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 added in v1.786.82

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 added in v1.786.32

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 svc.tasks). Empty = a surface
	// that consumes control from the event stream instead (today's @hanzo/dev).
	TaskWorkflowID string
	TaskRunID      string
}

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. Tenant isolation is the Org column, enforced on every query exactly like agents/runs — one file (agents.db), tenancy is the org.

type SessionFilter added in v1.786.32

type SessionFilter struct {
	Root   string
	Parent string
	Status string
	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.

type Store

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

Store is the agents database. ONE SQLite file ({DataDir}/agents.db) holds every org's records; tenancy is the org column.

func (*Store) AppendEvent added in v1.786.32

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

AppendEvent inserts one event, allocating the next per-session Seq. The store runs on a single connection (SetMaxOpenConns(1)) so the read-then-write of the max seq is serialised; the UNIQUE(session_id,seq) index is the final backstop. 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) Close

func (s *Store) Close() error

func (*Store) CountChildren added in v1.786.32

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 added in v1.786.32

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 added in v1.786.32

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 added in v1.786.32

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) 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) EventCountsByRoot added in v1.786.32

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) GetSession added in v1.786.32

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) InsertRun

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

InsertRun records one agent execution.

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) ListEvents added in v1.786.32

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 added in v1.786.32

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) 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 added in v1.786.32

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) ListTree added in v1.786.32

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 added in v1.786.32

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 added in v1.786.32

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) Update

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

Update overwrites the mutable fields of an existing agent.

func (*Store) UpdateSession added in v1.786.32

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

UpdateSession persists status/title/ended_at for an existing (org,id) session. Scoped by org so a cross-tenant id can never mutate another's session.

type TaskController added in v1.786.32

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