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
- func Mount(app *zip.App, deps cloud.Deps) error
- func Shutdown(ctx context.Context) error
- type Agent
- type Event
- type Run
- type Session
- type SessionFilter
- type Store
- func (s *Store) AppendEvent(ctx context.Context, e Event) (Event, error)
- func (s *Store) Close() error
- func (s *Store) CountChildren(ctx context.Context, org, id string) (int, error)
- func (s *Store) CountEvents(ctx context.Context, org, sessionID string) (int, error)
- func (s *Store) CountLongRunning(ctx context.Context, org string) (int, error)
- func (s *Store) CountRuns(ctx context.Context, org, agent string) (int, error)
- func (s *Store) Create(ctx context.Context, a Agent) error
- func (s *Store) CreateSession(ctx context.Context, x Session) error
- func (s *Store) Delete(ctx context.Context, org, name string) (bool, error)
- func (s *Store) EventCountsByRoot(ctx context.Context, org, root string) (map[string]int, error)
- func (s *Store) Get(ctx context.Context, org, name string) (Agent, error)
- func (s *Store) GetSession(ctx context.Context, org, id string) (Session, error)
- func (s *Store) InsertRun(ctx context.Context, r Run) error
- func (s *Store) List(ctx context.Context, org string) ([]Agent, error)
- func (s *Store) ListEvents(ctx context.Context, org, sessionID string, since int64, limit int) ([]Event, error)
- func (s *Store) ListLongRunning(ctx context.Context) ([]Agent, error)
- func (s *Store) ListRuns(ctx context.Context, org, agent string, limit int) ([]Run, error)
- func (s *Store) ListSessions(ctx context.Context, org string, f SessionFilter) ([]Session, error)
- func (s *Store) ListTree(ctx context.Context, org, root string, cap int) ([]Session, error)
- func (s *Store) Resolve(ctx context.Context, org, ref string) (Agent, error)
- func (s *Store) RunsSince(ctx context.Context, org string, since int64, limit int) ([]Run, error)
- func (s *Store) Update(ctx context.Context, a Agent) error
- func (s *Store) UpdateSession(ctx context.Context, x Session) error
- type TaskController
Constants ¶
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.
const ( CmdPause = "pause" CmdResume = "resume" CmdStop = "stop" CmdMessage = "message" )
Control commands — the closed vocabulary of remote steering.
const ( StatusRunning = "running" StatusPaused = "paused" StatusDone = "done" StatusError = "error" )
Session status values. running/paused are live; done/error are terminal.
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 ¶
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
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
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
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) CountChildren ¶ added in v1.786.32
CountChildren returns how many DIRECT children a session has (its fan-out).
func (*Store) CountEvents ¶ added in v1.786.32
CountEvents returns how many events a session has (the list rollup).
func (*Store) CountLongRunning ¶ added in v1.786.32
CountLongRunning returns how many scheduled long-running agents an org has — used to cap an org's scheduler footprint at create time.
func (*Store) Create ¶
Create inserts one agent. A UNIQUE(org,name) violation surfaces as errConflict.
func (*Store) CreateSession ¶ added in v1.786.32
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) EventCountsByRoot ¶ added in v1.786.32
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 ¶
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
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) 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
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) ListSessions ¶ added in v1.786.32
ListSessions returns an org's sessions per filter, newest first, capped.
func (*Store) ListTree ¶ added in v1.786.32
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
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
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.
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.