Documentation
¶
Overview ¶
Package agentsdk provides the Go SDK for building Airlock agents.
This package will contain the client library that agents use to communicate with the Airlock platform — registering connections, receiving triggers, and streaming run results.
Index ¶
- Constants
- Variables
- func GetDeps[T any](ctx context.Context) T
- func IsLikelySecret(v string) bool
- func IsValidatingMigrations() bool
- func RequestJSON[T any](ctx context.Context, h *ConnectionHandle, opts RequestOpts) (T, error)
- func ResolveDisplayPart(p *DisplayPart)
- func WithCaller(ctx context.Context, c Caller) context.Context
- type Access
- type Action
- type Agent
- func (a *Agent) AddInstruction(p *Instruction)
- func (a *Agent) AddSensitive(values ...string)
- func (a *Agent) CancelSchedule(ctx context.Context, id string) error
- func (a *Agent) CheckFileAccess(ctx context.Context, path string, op FileOp) error
- func (a *Agent) CopyFile(ctx context.Context, src, dst string) error
- func (a *Agent) DB() *AgentDB
- func (a *Agent) DeleteFile(ctx context.Context, path string) error
- func (a *Agent) Embed(ctx context.Context, input goai.EmbedInput) (*goai.EmbedResult, error)
- func (a *Agent) EmbeddingModel(ctx context.Context, slug string) model.EmbeddingModel
- func (a *Agent) GenerateImage(ctx context.Context, input goai.ImageInput) (*goai.ImageResult, error)
- func (a *Agent) GenerateSpeech(ctx context.Context, input goai.SpeechInput) (*goai.SpeechResult, error)
- func (a *Agent) GenerateText(ctx context.Context, input stream.Input) (*goai.GenerateTextResult, error)
- func (a *Agent) Handler() http.Handler
- func (a *Agent) ImageModel(ctx context.Context, slug string) model.ImageModel
- func (a *Agent) LLM(ctx context.Context, slug string) stream.Model
- func (a *Agent) ListDir(ctx context.Context, path string, opts ListOpts) ([]FileInfo, error)
- func (a *Agent) ListSchedules(ctx context.Context, f ListSchedulesFilter) ([]ScheduledFire, error)
- func (a *Agent) Logger(ctx context.Context) *zap.Logger
- func (a *Agent) MigrationContext(ctx context.Context) context.Context
- func (a *Agent) OpenFile(ctx context.Context, path string) (io.ReadCloser, error)
- func (a *Agent) OpenFileRange(ctx context.Context, path string, start, end int64) (io.ReadCloser, error)
- func (a *Agent) ReadFile(ctx context.Context, path string) ([]byte, error)
- func (a *Agent) ReadRange(ctx context.Context, path string, start, end int64) ([]byte, error)
- func (a *Agent) RegisterConnection(c *Connection) *ConnectionHandle
- func (a *Agent) RegisterCron(c *Cron)
- func (a *Agent) RegisterDirectory(path string, opts DirectoryOpts)
- func (a *Agent) RegisterEnvVar(e *EnvVar) *EnvVarHandle
- func (a *Agent) RegisterExecEndpoint(e *ExecEndpoint) *ExecHandle
- func (a *Agent) RegisterMCP(m *MCP) *MCPHandle
- func (a *Agent) RegisterModel(slot *ModelSlot)
- func (a *Agent) RegisterRoute(r *Route)
- func (a *Agent) RegisterSchedule(s *Schedule)
- func (a *Agent) RegisterTool(t tool.Tool, access Access, opts ...RegisterOption)
- func (a *Agent) RegisterTopic(t *Topic) *TopicHandle
- func (a *Agent) RegisterWebhook(w *Webhook)
- func (a *Agent) ScheduleAt(ctx context.Context, req ScheduleAtRequest) (string, error)
- func (a *Agent) Seal(ctx context.Context, plaintext string) (string, error)
- func (a *Agent) Serve()
- func (a *Agent) ShareFileURL(ctx context.Context, path string, ttl time.Duration) (*ShareFileResponse, error)
- func (a *Agent) SpeechModel(ctx context.Context, slug string) model.SpeechModel
- func (a *Agent) StatFile(ctx context.Context, path string) (FileInfo, error)
- func (a *Agent) StreamText(ctx context.Context, input stream.Input) (*stream.Result, error)
- func (a *Agent) SyncDown(ctx context.Context, prefix, localDir string) error
- func (a *Agent) SyncUp(ctx context.Context, localDir, prefix string) error
- func (a *Agent) Transcribe(ctx context.Context, input goai.TranscribeInput) (*goai.TranscriptionResult, error)
- func (a *Agent) TranscriptionModel(ctx context.Context, slug string) model.TranscriptionModel
- func (a *Agent) Unseal(ctx context.Context, sealed string) (string, error)
- func (a *Agent) WebSearch(ctx context.Context, slug string, req websearch.Request) (*websearch.Response, error)
- func (a *Agent) WriteFile(ctx context.Context, path string, data io.Reader, contentType string) (FileInfo, error)
- type AgentDB
- func (a *AgentDB) BeginTx(ctx context.Context, opts *sql.TxOptions) (*AgentTx, error)
- func (a *AgentDB) ExecContext(ctx context.Context, query string, args ...any) (sql.Result, error)
- func (a *AgentDB) PingContext(ctx context.Context) error
- func (a *AgentDB) PrepareContext(ctx context.Context, query string) (*sql.Stmt, error)
- func (a *AgentDB) QueryContext(ctx context.Context, query string, args ...any) (*sql.Rows, error)
- func (a *AgentDB) QueryRowContext(ctx context.Context, query string, args ...any) *sql.Row
- func (a *AgentDB) Underlying() *sql.DB
- type AgentTx
- func (a *AgentTx) Commit() error
- func (a *AgentTx) ExecContext(ctx context.Context, query string, args ...any) (sql.Result, error)
- func (a *AgentTx) PrepareContext(ctx context.Context, query string) (*sql.Stmt, error)
- func (a *AgentTx) QueryContext(ctx context.Context, query string, args ...any) (*sql.Rows, error)
- func (a *AgentTx) QueryRowContext(ctx context.Context, query string, args ...any) *sql.Row
- func (a *AgentTx) Rollback() error
- func (a *AgentTx) Underlying() *sql.Tx
- type AuthInjection
- type AuthInjectionType
- type AuthRequiredError
- type Caller
- type Capabilities
- type Config
- type Connection
- type ConnectionAuth
- type ConnectionDef
- type ConnectionHandle
- type ConnectionResponse
- type CreateRunRequest
- type CreateRunResponse
- type Cron
- type DirPath
- type Directory
- type DirectoryDef
- type DirectoryOpts
- type DirectoryScope
- type DisplayPart
- type EnvVar
- type EnvVarDef
- type EnvVarHandle
- type EnvVarValueResponse
- type EventWriter
- type ExecCommand
- type ExecEndpoint
- type ExecEndpointDef
- type ExecError
- type ExecExit
- type ExecHandle
- type ExecRequest
- type ExecResult
- type ExecStream
- type FileInfo
- type FileOp
- type FilePath
- type HTTPRequest
- type HTTPResponse
- type HTTPSessionStore
- type Instruction
- type InstructionDef
- type LLMProxyRequest
- type ListOpts
- type ListSchedulesFilter
- type LogEntry
- type LogLevel
- type MCP
- type MCPAuth
- type MCPAuthStatus
- type MCPContent
- type MCPDef
- type MCPHandle
- type MCPToolCallRequest
- type MCPToolCallResponse
- type MCPToolSchema
- type MockAirlock
- type MockRequest
- type ModelCapability
- type ModelProxyRequest
- type ModelSlot
- type ModelSlotDef
- type PrintRequest
- type PromptData
- type PromptInput
- type ProxyRequest
- type RegisterOption
- type RequestOpts
- type Route
- type RouteDef
- type RouteHandlerFunc
- type RunCompleteRequest
- type Schedule
- type ScheduleAtRequest
- type ScheduleHandlerDef
- type ScheduleHandlerFunc
- type ScheduledFire
- type ScheduledFireRef
- type SealRequest
- type SealResponse
- type SearchProxyRequest
- type SessionCompactRequest
- type ShareFileRequest
- type ShareFileResponse
- type SiblingHandle
- type SiblingInfo
- type SyncRequest
- type SyncResponse
- type ToolDef
- type Topic
- type TopicDef
- type TopicHandle
- type UnsealRequest
- type UnsealResponse
- type User
- type Webhook
- type WebhookDef
- type WebhookHandlerFunc
Constants ¶
const ( // ErrorKindPlatform: failure upstream of the agent's own code. LLM // provider 4xx, sol/goai stream errors, request transport (body read). // The agent's code couldn't have prevented or fixed this — the "Fix // this error" workflow on the run page is hidden for these. ErrorKindPlatform = "platform" // ErrorKindAgent: failure from agent-defined code paths. Webhook/cron // handlers returning err, panics in user code recovered by the SDK, // post-LLM bookkeeping that hit something the agent owns. The Fix // workflow targets exactly these. ErrorKindAgent = "agent" )
Error kinds passed in RunCompleteRequest.ErrorKind. The agentsdk side classifies structurally — by call-site, not by error string — so airlock can avoid pattern-matching at all.
const HTMXVersion = "2.0.10"
HTMXVersion is the version of htmx the asset route serves.
const MaxBufferedResponseBytes = 20 << 20 // 20 MiB
MaxBufferedResponseBytes is the cap Run enforces per stream. Mirrors the airlock-side constant of the same name. Documented here so tools that wrap Run can surface the limit in their own error messages without importing airlock packages.
const Version = "0.4.0-rc.7"
Version is the agentsdk API version. Reported to Airlock during sync. Bump on breaking changes — see AGENTS.md for versioning rules. Pre-commit gate enforces Version > latest git tag in this repo.
Variables ¶
var Assets = struct { HTMX string // versioned path to the bundled htmx (e.g. /__air/assets/htmx-2.0.10.min.js) }{ HTMX: assetsPathPrefix + htmxAssetName, }
Assets is the catalog of framework JS bundled with agentsdk and served same-origin under /__air/assets/. The path carries the embedded version segment ("htmx-2.0.10.min.js"), so bumping agentsdk yields a fresh URL that's never been browser-cached — the immutable Cache-Control on prior versions can stay in place. Use it in templ layouts:
<script src={ agentsdk.Assets.HTMX }></script>
/__air/assets/* is framework-reserved. For your own static files (icons, images, page-specific CSS, fonts), embed them and serve via a RegisterRoute under a different prefix like /static/{name} (which is how the scaffold serves the compiled Tailwind stylesheet).
var ErrInvalidPath = errors.New("agentsdk: invalid path")
ErrInvalidPath is returned for paths that fail normalization (missing leading '/', empty segments, '..' segments, etc.).
var ErrNotFound = errors.New("agentsdk: file not found")
ErrNotFound is returned by CheckFileAccess and the storage methods for both "directory not registered" and "caller does not have access" — the two cases are deliberately indistinguishable at the public surface so path-guessing leaks no information about what exists.
var ErrOutputTooLarge = errors.New("agentsdk: response exceeded 20 MiB buffer cap; Run/Request are for structured small responses (JSON, HTML, CLI summaries) — use RunStream/RequestStream for any data download")
ErrOutputTooLarge is returned by Run / Request when the response exceeds the 20 MiB buffered cap. The error message points the caller at the streaming variant as the resolution.
Functions ¶
func GetDeps ¶
GetDeps retrieves the typed Deps struct from the Agent bound to ctx. Panics if no agent is bound (must be called from inside a handler), if Deps is nil, or if the type doesn't match. Used by handlers that need the builder's pre-registered application state (connection/MCP handles, config, etc.) — particularly VM functions defined in separate packages where the `agent` variable isn't in scope.
func IsLikelySecret ¶ added in v0.2.3
IsLikelySecret reports whether v looks like a real credential rather than a low-entropy placeholder or common phrase. Used to gate auto-redaction so values like "password", "true", or "hello world" don't end up in the redact set and nuke ordinary words from LLM input.
Heuristic, calibrated empirically against real API-key shapes (sk-…, bb_live_…, AKIA…, ghp_…, JWTs, telegram bot tokens):
- Length ≥ 16 — every common credential format clears this; common phrases ("password1234", "hello world!") don't.
- Reject all-digit strings and JSON literals regardless of length.
- Reject very-low-entropy strings (< 2.0 bits/char) to filter out "aaaaaaaaaaaaaaaa"-style repeats. Real keys score 4–5 bits/char.
This is a heuristic, not a security boundary. Operators who paste a real key are auto-protected; operators who paste a placeholder don't poison their own logs.
func IsValidatingMigrations ¶
func IsValidatingMigrations() bool
IsValidatingMigrations reports whether the agent is running in migration validation mode (AGENT_VALIDATE_MIGRATIONS=1). Go migrations that touch external services (S3, Airlock API, connection credentials) should return early when this is true — those services are not available during build-time validation.
func RequestJSON ¶ added in v0.3.0
func RequestJSON[T any](ctx context.Context, h *ConnectionHandle, opts RequestOpts) (T, error)
RequestJSON is the typed twin of ConnectionHandle.Request. It sends the request, decodes the response body into T, and returns it. Empty body (204 No Content, zero-length 200) decodes to a zero T rather than the standard library's "unexpected end of JSON input" — matches the behaviour of the JS-side conn_<slug>.requestJSON binding, where an empty upstream body surfaces as null.
Auth and HTTP-error semantics are inherited from Request: returns *AuthRequiredError on 402 (use IsAuthRequired to test), and an opaque error carrying the upstream status + body on any other non-2xx.
Methods can't have type parameters in Go, so this is a free function over a *ConnectionHandle rather than a method on it.
state, err := agentsdk.RequestJSON[PlaybackState](ctx, conn,
agentsdk.RequestOpts{Path: "/v1/me/player"})
func ResolveDisplayPart ¶
func ResolveDisplayPart(p *DisplayPart)
resolveDisplayPart infers missing fields on a DisplayPart from available data. 1. If Data is set but MimeType is empty → detect from bytes. 2. If MimeType is set but Type is empty → infer from MIME prefix. 3. If Filename is empty and part has media → generate from type + mimeType.
Types ¶
type Access ¶
type Access string
Access defines who can reach a tool, connection, MCP, topic, or storage zone.
type Action ¶
type Action struct {
Type string `json:"type"`
Timestamp time.Time `json:"timestamp"`
DurationMs int64 `json:"durationMs"`
Request any `json:"request,omitempty"`
Response any `json:"response,omitempty"`
Error string `json:"error,omitempty"`
}
Action records a single operation performed during a Run.
type Agent ¶
type Agent struct {
// Deps holds application-level dependencies (connection handles, MCP handles, etc.).
// The builder defines their own typed struct and assigns it here.
// Access from handlers via GetDeps[T](run).
Deps any
// contains filtered or unexported fields
}
Agent is a long-lived singleton, one per container. Created once at startup via New(), lives for the lifetime of the process.
func AgentFromContext ¶
AgentFromContext returns the *Agent associated with a handler's ctx. Returns nil if ctx wasn't produced by a handler (e.g. a plain context.Background() in test code).
func AgentFromMigrationContext ¶
AgentFromMigrationContext returns the Agent associated with a migration context. Panics if called outside of a migration — migrations receive the context via goose, which propagates it from autoMigrate.
func New ¶
New creates an Agent by reading required environment variables. Panics if AIRLOCK_AGENT_ID, AIRLOCK_API_URL, or AIRLOCK_AGENT_TOKEN is missing. Panics if Config.Description is empty.
func (*Agent) AddInstruction ¶
func (a *Agent) AddInstruction(p *Instruction)
AddInstruction appends a system-prompt fragment that Airlock will include for callers whose resolved access on this agent matches one of the fragment's Access levels. An empty Access slice means the fragment applies to every caller.
Fragments accumulate in registration order and are joined with "\n\n" by Airlock at run dispatch, then appended to the sync-rendered system prompt. Only /prompt-triggered runs (web + bridge) receive instructions — webhook and cron handlers run arbitrary Go code and build their own prompts.
func (*Agent) AddSensitive ¶
AddSensitive registers values that should be redacted from LLM messages. Bypasses the IsLikelySecret filter — use only for values the framework knows are sensitive (e.g. the agent token); otherwise prefer maybeAddSensitive.
func (*Agent) CancelSchedule ¶
CancelSchedule removes a pending fire by id. It is a no-op if the fire already fired or never existed.
func (*Agent) CheckFileAccess ¶ added in v0.2.0
CheckFileAccess is the single gate for paths that arrived from untrusted territory: VM run_js code, HTTP requests, tool inputs from the LLM. Builder Go code that constructs paths itself bypasses this check by calling OpenFile/ReadFile/WriteFile/etc. directly.
Returns ErrInvalidPath for malformed paths, ErrNotFound for everything else (denied OR no covering directory). The two latter cases are indistinguishable on purpose so path-guessing reveals nothing.
func (*Agent) CopyFile ¶ added in v0.2.0
CopyFile server-side-copies a file from src to dst. Both paths are absolute and may live under different directories. Trusted: no access check.
func (*Agent) DB ¶
DB returns a lazily-initialized *AgentDB from AIRLOCK_DB_URL. Returns nil if the env var is not set (DB is optional).
AgentDB implements the same DBTX interface that sqlc-generated New() takes, so `mygen.New(agent.DB())` works unchanged. The wrapper is the extension point through which the framework can later record query activity onto the run carried by ctx.
func (*Agent) DeleteFile ¶ added in v0.2.0
DeleteFile removes a file. Idempotent — missing files do not error. Trusted: no access check.
func (*Agent) Embed ¶
func (a *Agent) Embed(ctx context.Context, input goai.EmbedInput) (*goai.EmbedResult, error)
Embed computes embeddings. A missing Model defaults to the agent's embedding-capability proxy model.
func (*Agent) EmbeddingModel ¶
EmbeddingModel returns an embedding model for the registered slot `slug`. Panics unless `slug` is registered with CapEmbedding.
func (*Agent) GenerateImage ¶
func (a *Agent) GenerateImage(ctx context.Context, input goai.ImageInput) (*goai.ImageResult, error)
GenerateImage generates an image. A missing Model defaults to the agent's image-capability proxy model.
func (*Agent) GenerateSpeech ¶
func (a *Agent) GenerateSpeech(ctx context.Context, input goai.SpeechInput) (*goai.SpeechResult, error)
GenerateSpeech synthesizes speech. A missing Model defaults to the agent's speech-capability proxy model.
func (*Agent) GenerateText ¶
func (a *Agent) GenerateText(ctx context.Context, input stream.Input) (*goai.GenerateTextResult, error)
GenerateText runs a (multi-step, if input.MaxSteps>1) text generation. Tools in input.Tools — typically the same tool.Tool values passed to RegisterTool — run under the resolved run, so they can reach agent facilities.
func (*Agent) Handler ¶
Handler builds the agent's HTTP mux: the framework routes (/prompt, /webhook, /fire, /refresh, /health, the A2A and asset endpoints) plus every route registered via RegisterRoute, each wrapped with the lazy-run + logging middleware. Serve installs it after syncing with Airlock.
Handler does not sync with Airlock and does not listen — it just returns the mux. Tests use it to exercise routes through the real dispatch (including {param} extraction) with httptest. A test that needs the synced prompt data or MCP schemas a handler reads must call syncWithAirlock first.
func (*Agent) ImageModel ¶
ImageModel returns an image generation model for the registered slot `slug`. Panics unless `slug` is registered with CapImage.
func (*Agent) LLM ¶
LLM returns a streaming chat model for the registered slot `slug`. The slot's declared capability (CapText or CapVision) selects the model type; the operator binds a concrete model to the slot in the Airlock UI, falling back to the agent's per-capability default and then the system default. Panics if `slug` is empty, not registered with RegisterModel, or registered with a non-chat capability. Pass the returned model the same ctx when calling Stream.
func (*Agent) ListDir ¶ added in v0.2.0
ListDir enumerates files under `path`. Trusted: no access check. The empty string lists the agent root.
func (*Agent) ListSchedules ¶
func (a *Agent) ListSchedules(ctx context.Context, f ListSchedulesFilter) ([]ScheduledFire, error)
ListSchedules returns the agent's pending fires, optionally for one slug.
func (*Agent) Logger ¶ added in v0.3.0
Logger returns the zap logger for the current handler invocation. Bind it once at handler entry — `log := a.Logger(ctx)` — and use it throughout; the ctx is consumed here to resolve the run, so callers don't thread it per line.
When ctx carries a run, the returned logger is tagged with run_id/agent_id and tees every line two ways: structured JSON to container stdout (what an enterprise log pipeline scrapes) and a bounded per-run buffer that Airlock keeps as the run's log record (a failed run's copy also feeds the Fix-this-error builder). Outside a run (init, migrations, detached goroutines) it returns the plain stdout logger — no run to attach to.
It is a real *zap.Logger: use zap.String/zap.Int/zap.Error/... for structured fields, and the level-named methods (Info/Warn/Error/Debug) for severity.
func (*Agent) MigrationContext ¶
MigrationContext returns ctx with this agent attached, so goose Up/Down calls run the agent's Go migrations — those fetch the agent via AgentFromMigrationContext. autoMigrate uses this for the baked /migrations; tests use it to apply db/migrations against a test database (see agentsdk/agenttest.UseDB).
func (*Agent) OpenFile ¶ added in v0.2.0
OpenFile streams a file. The returned ReadCloser must be closed by the caller. Trusted: no access check. Used by builder Go code that constructs paths itself.
func (*Agent) OpenFileRange ¶ added in v0.3.0
func (a *Agent) OpenFileRange(ctx context.Context, path string, start, end int64) (io.ReadCloser, error)
OpenFileRange streams the inclusive byte range [start, end] of a file (HTTP Range semantics). The returned ReadCloser must be closed by the caller. Trusted: no access check.
func (*Agent) ReadFile ¶ added in v0.2.0
ReadFile reads a file fully into memory. For very large files prefer OpenFile + io.Copy. Trusted: no access check.
func (*Agent) ReadRange ¶ added in v0.3.0
ReadRange reads the inclusive byte range [start, end] of a file fully into memory. Trusted: no access check.
func (*Agent) RegisterConnection ¶
func (a *Agent) RegisterConnection(c *Connection) *ConnectionHandle
RegisterConnection registers an outgoing service connection and returns a handle for proxied requests. Synced to Airlock on Serve(). Use the returned handle for compile-time-bound proxy calls:
gmail := agent.RegisterConnection(&agentsdk.Connection{
Slug: "gmail", Name: "Gmail", BaseURL: "https://gmail.googleapis.com", ...,
})
body, err := gmail.Request(ctx, agentsdk.RequestOpts{Path: "/messages"})
func (*Agent) RegisterCron ¶
RegisterCron installs a recurring cron. Schedule is a standard cron expression (e.g. "0 9 * * *"). Synced to Airlock on Serve() so the scheduler fires it. The slug shares one namespace with RegisterSchedule.
func (*Agent) RegisterDirectory ¶ added in v0.2.0
func (a *Agent) RegisterDirectory(path string, opts DirectoryOpts)
RegisterDirectory declares an S3-backed directory at the given path, gated by independent Read / Write / List caps. Inside run_js the flat verbs (fileRead, fileWrite, fileList, fileDelete, fileStat, fileReadBytes, fileExists) check the calling run's access against the directory's caps via CheckFileAccess.
Path is S3-style: no leading '/', no trailing '/', e.g. "uploads" or "reports/q1". A leading slash is rejected — the LLM and builders share one canonical form. Files under the directory are addressed as "uploads/doc.pdf", never "/uploads/doc.pdf".
Builder Go code reads and writes the directory through the trusted file API (agent.OpenFile / ReadFile / WriteFile / StatFile / ListDir / DeleteFile) — these methods do NOT call CheckFileAccess, on the principle that builder code that constructs paths itself is trusted. When a builder tool accepts a path from the LLM (typed as `string` on an Input struct), the builder must call agent.CheckFileAccess explicitly before passing the path anywhere.
The framework reserves "tmp" for its own scratch (truncated tool output, generated media) at Read=Write=List=AccessUser. Builders may call RegisterDirectory("tmp", ...) to override the description; the access caps are kept at the framework's defaults.
agent.RegisterDirectory("uploads", agentsdk.DirectoryOpts{
Read: agentsdk.AccessUser, Write: agentsdk.AccessUser, List: agentsdk.AccessUser,
Description: "User uploads",
})
err := agent.WriteFile(ctx, "uploads/doc.pdf", reader, "application/pdf")
func (*Agent) RegisterEnvVar ¶ added in v0.2.3
func (a *Agent) RegisterEnvVar(e *EnvVar) *EnvVarHandle
RegisterEnvVar declares an operator-configured environment variable the agent will read at runtime. Returned handle's Get(ctx) fetches the value through Airlock; operators populate the value via the agent's "Environment" tab in the admin UI.
See the EnvVar type doc for the Secret flag's semantics (write-only UI + redaction).
bbKey := agent.RegisterEnvVar(&agentsdk.EnvVar{
Slug: "browserbase_api_key",
Description: "Browserbase API key",
Secret: true,
})
// later, inside a tool:
key, err := bbKey.Get(ctx)
func (*Agent) RegisterExecEndpoint ¶ added in v0.3.0
func (a *Agent) RegisterExecEndpoint(e *ExecEndpoint) *ExecHandle
RegisterExecEndpoint declares a remote command target the agent can reach (e.g. a VPS via SSH, a CI runner). Airlock owns the transport and credentials — the agent's main() only declares slug/description/ access. Returns an *ExecHandle for compile-time-bound calls:
ci := agent.RegisterExecEndpoint(&agentsdk.ExecEndpoint{
Slug: "ci-runner",
Description: "Self-hosted GitHub Actions runner",
Access: agentsdk.AccessAdmin,
})
res, err := ci.Run(ctx, agentsdk.ExecCommand{Command: "kick-build"})
Default access is AccessAdmin (exec hands arbitrary commands to a real machine — admin-only by default). AccessPublic is silently demoted to AccessUser with a startup warning: exec endpoints are never reachable by unauthenticated callers, period. The demotion is friendly because copy-pasting from RegisterRoute (where Public is meaningful) is a believable mistake.
func (*Agent) RegisterMCP ¶
RegisterMCP registers a remote MCP server dependency and returns a handle for calling its tools. Synced to Airlock on Serve(). Use the returned handle for compile-time-bound tool calls:
github := agent.RegisterMCP(&agentsdk.MCP{Slug: "github", URL: "https://api.github.com/mcp"})
result, err := github.CallTool(ctx, "search_repos", args)
func (*Agent) RegisterModel ¶
RegisterModel declares a named model slot the agent uses at runtime via agent.LLM(ctx, slug) / agent.ImageModel(ctx, slug) / etc. The slot's Capability is the single source of truth for the model type — the getters take only a slug and read the capability from here. The admin binds a concrete model to each slot in the Airlock UI; an unbound slot falls back to the agent's per-capability default and then the system default for the slot's declared capability. Call before Serve().
Registration is required: every slug passed to a model getter must be declared here first. Calling a getter with an unregistered (or empty) slug panics — a missing declaration is a programmer error, not a silent fall-through to a default model.
func (*Agent) RegisterRoute ¶
RegisterRoute installs a custom HTTP route served by this agent and proxied via Airlock's subdomain routing.
func (*Agent) RegisterSchedule ¶
RegisterSchedule installs a handler for runtime-armed one-shot fires (see agent.ScheduleAt). The slug shares one namespace with RegisterCron.
func (*Agent) RegisterTool ¶
func (a *Agent) RegisterTool(t tool.Tool, access Access, opts ...RegisterOption)
RegisterTool registers a goai tool.Tool the LLM can invoke, at the given access level (empty defaults to AccessUser). Build the tool once with tool.Typed[In,Out] (or tool.New) and pass the same value here and to the agent's GenerateText/StreamText sub-calls.
calc := tool.Typed[CalcIn, CalcOut]("calculator").
Description("Evaluate an expression.").
Execute(doCalc).
Build()
agent.RegisterTool(calc, agentsdk.AccessUser)
func (*Agent) RegisterTopic ¶
func (a *Agent) RegisterTopic(t *Topic) *TopicHandle
RegisterTopic declares a topic the agent can publish notifications to. Synced to Airlock on Serve(). Use the returned *TopicHandle for compile-time-bound publishing:
alerts := agent.RegisterTopic(&agentsdk.Topic{Slug: "alerts", Description: "System alerts"})
alerts.Publish(ctx, []DisplayPart{{Type: "text", Text: "Server restarted"}})
func (*Agent) RegisterWebhook ¶
RegisterWebhook installs a webhook handler at /webhook/{Path}. Synced to Airlock on Serve() so external callers can reach it via the agent's webhook ingress endpoint.
func (*Agent) ScheduleAt ¶
ScheduleAt arms a one-shot fire of a registered handler at fireAt and returns the fire id. Store that id with your per-instance data in the agent's own DB; the fire handler recovers it via ScheduleFromContext. The slug must name a registered cron or schedule.
func (*Agent) Seal ¶ added in v0.3.0
Seal encrypts plaintext via Airlock and returns an opaque sealed string the agent persists in its OWN storage (its database, a file, wherever its domain model fits — agent-wide, per-user, per-conversation). The agent never holds the encryption key: Airlock seals and opens on its behalf and binds the ciphertext to this agent, so no other agent can Unseal it even if the sealed value leaks.
Use this for secrets the agent generates at runtime and must reuse across runs — e.g. a session token minted by an interactive login. The plaintext is registered for redaction (heuristic-gated, like a Secret env var) so it is stripped from LLM input.
func (*Agent) Serve ¶
func (a *Agent) Serve()
Serve starts the agent HTTP server. Blocks until SIGINT/SIGTERM. Listens on AIRLOCK_ADDR env var or :8080. Before starting, syncs connections/webhooks/crons with Airlock.
func (*Agent) ShareFileURL ¶ added in v0.2.1
func (a *Agent) ShareFileURL(ctx context.Context, path string, ttl time.Duration) (*ShareFileResponse, error)
ShareFileURL returns a presigned, unauthenticated, time-limited URL pointing at the given storage path. ttl <= 0 picks the server default (1h); the server caps anything over 24h. The URL is signed for the public S3 endpoint when configured, so it works from outside the docker network (browsers, LLM providers, external tools). Trusted: no access check — the JS binding gates LLM-supplied paths via CheckFileAccess.
Use cases: embedding in markdown ([file](url)), sharing externally, cases where the agent's authenticated /__air/storage subdomain route isn't reachable for the recipient. For showing files in chat, prefer output({type:"file", source:path}).
func (*Agent) SpeechModel ¶
SpeechModel returns a text-to-speech model for the registered slot `slug`. Panics unless `slug` is registered with CapSpeech.
func (*Agent) StatFile ¶ added in v0.2.0
StatFile returns metadata for a file. Trusted: no access check.
func (*Agent) StreamText ¶
StreamText is the streaming counterpart of GenerateText.
func (*Agent) SyncDown ¶ added in v0.2.1
SyncDown copies every file under `prefix` from S3-backed storage into `localDir`. After the call, files that exist in S3 are present locally at the matching subpath (S3 → local mirror within the prefix). Files that exist *only* locally are left in place — this is sync, not a destructive mirror, so the runtime image's seeded /var/agent/bin survives the very first call when S3 is still empty.
Sync semantics: a file is overwritten locally iff the remote object's LastModified is newer than the local file's mtime, or the local file is missing / a different size. After overwriting, the local mtime is set to the remote's LastModified so subsequent SyncUp/SyncDown rounds don't churn the same files back and forth.
Files synced down are chmodded to 0755. The use case is binaries + data caches — both fine with the executable bit set. Set the mode explicitly after the call if you need finer control.
Trusted: no access check (builder code that constructs paths itself).
Use case: pair with SyncUp in a cron handler to persist self-updating binaries (e.g. `bun upgrade`, `freshclam`) across container restarts — the running container's local copy is the working copy, S3 is the durable record. See the agent-builder prompt for a full worked example.
func (*Agent) SyncUp ¶ added in v0.2.1
SyncUp is the reverse of SyncDown: walks `localDir` and uploads every file to the matching subpath under `prefix`. A file is uploaded iff the remote is missing, a different size, or older than the local mtime. After upload, the local mtime is matched to the resulting S3 LastModified so subsequent rounds don't churn.
Last-writer-wins semantics on multi-replica: two replicas concurrently uploading the same path will end up with whichever finished last. That's correct for self-updates (both replicas converge to the same new version anyway). For shared mutable state with concurrent writers, use the agent's Postgres schema instead — files are for blobs, rows are for shared state.
Trusted: no access check (builder code that constructs paths itself).
func (*Agent) Transcribe ¶
func (a *Agent) Transcribe(ctx context.Context, input goai.TranscribeInput) (*goai.TranscriptionResult, error)
Transcribe converts speech to text. A missing Model defaults to the agent's transcription-capability proxy model.
func (*Agent) TranscriptionModel ¶
TranscriptionModel returns a speech-to-text model for the registered slot `slug`. Panics unless `slug` is registered with CapTranscription.
func (*Agent) Unseal ¶ added in v0.3.0
Unseal reverses Seal, returning the original plaintext. It fails if the sealed value was produced for a different agent or is corrupt. The recovered plaintext is registered for redaction.
func (*Agent) WebSearch ¶
func (a *Agent) WebSearch(ctx context.Context, slug string, req websearch.Request) (*websearch.Response, error)
WebSearch runs a web search through the registered CapSearch slot `slug`. The admin binds a search provider to the slot in the Airlock UI; an unbound slot resolves to the agent's configured search provider, then the system default — the same cascade as an unbound model slot. The call is proxied through Airlock (no search API keys in the container). Panics if `slug` is empty or not registered with RegisterModel as CapSearch.
Prefer searching directly and feeding the results into GenerateText over exposing search as an LLM tool: it's one round-trip instead of a model→ Airlock→model detour, and the search always runs (a tool the model may decline to call doesn't). If you genuinely need the model to decide whether to search mid-conversation, wrap this in your own RegisterTool.
func (*Agent) WriteFile ¶ added in v0.2.0
func (a *Agent) WriteFile(ctx context.Context, path string, data io.Reader, contentType string) (FileInfo, error)
WriteFile writes data with the given content type. Returns the resulting FileInfo (path/filename/contentType/size/lastModified). Trusted: no access check.
For directories registered with a Scope, the destination path is rewritten to include a scope segment derived from the run's identity: "tmp/cat.jpg" with ScopeUser becomes "tmp/user-<id>/cat.jpg". The scoped path travels back via the returned FileInfo.Path; callers use that string from then on (CheckFileAccess parses the same segment to gate reads). Bare paths that already contain a "<kind>-<id>" scope segment are written as-is.
type AgentDB ¶
type AgentDB struct {
// contains filtered or unexported fields
}
AgentDB wraps the agent's *sql.DB and is the type returned by Agent.DB(). It implements the same DBTX interface that sqlc-generated New() functions take, so builder code that does `mygen.New(agent.DB())` keeps compiling.
Today AgentDB is a thin pass-through. The reason it exists is so the framework can later intercept queries at this layer (record an action on the run carried by ctx, surface query timings in the Runs UI, redact sensitive arguments) without breaking builders or sqlc-generated code. Builders that need the underlying driver-level handle for advanced cases (Stats, Driver, custom drivers) can reach it via Underlying().
func (*AgentDB) BeginTx ¶
BeginTx starts a transaction. The returned *AgentTx implements the same DBTX interface, so `mygen.New(tx)` and `q.WithTx(tx)` both work.
func (*AgentDB) ExecContext ¶
ExecContext satisfies sqlc's DBTX. Forwards to the underlying *sql.DB.
func (*AgentDB) PingContext ¶
PingContext checks database connectivity.
func (*AgentDB) PrepareContext ¶
PrepareContext satisfies sqlc's DBTX. Forwards to the underlying *sql.DB.
func (*AgentDB) QueryContext ¶
QueryContext satisfies sqlc's DBTX. Forwards to the underlying *sql.DB.
func (*AgentDB) QueryRowContext ¶
QueryRowContext satisfies sqlc's DBTX. Forwards to the underlying *sql.DB.
func (*AgentDB) Underlying ¶
Underlying returns the wrapped *sql.DB. Use this only when you need driver-level access that DBTX doesn't expose; otherwise stick to the AgentDB methods so future framework instrumentation applies to your code.
type AgentTx ¶
type AgentTx struct {
// contains filtered or unexported fields
}
AgentTx wraps a *sql.Tx with the same shape as AgentDB so sqlc's q.WithTx(tx) accepts it transparently.
func (*AgentTx) ExecContext ¶
ExecContext satisfies sqlc's DBTX.
func (*AgentTx) PrepareContext ¶
PrepareContext satisfies sqlc's DBTX.
func (*AgentTx) QueryContext ¶
QueryContext satisfies sqlc's DBTX.
func (*AgentTx) QueryRowContext ¶
QueryRowContext satisfies sqlc's DBTX.
func (*AgentTx) Underlying ¶
Underlying returns the wrapped *sql.Tx.
type AuthInjection ¶
type AuthInjection struct {
Type AuthInjectionType `json:"type"`
Name string `json:"name,omitempty"`
}
AuthInjection defines how auth credentials are injected into proxied requests. Name carries the header or query-parameter name depending on Type:
- api_key_header: header name (default "X-API-Key")
- query_param: query-string key (default "token")
- bearer / path_prefix: ignored
type AuthInjectionType ¶ added in v0.2.1
type AuthInjectionType string
AuthInjectionType selects how the proxy injects the stored credential into each upstream request.
const ( // AuthInjectBearer sets `Authorization: Bearer {token}`. AuthInjectBearer AuthInjectionType = "bearer" // AuthInjectAPIKey sets a custom header `{Name}: {token}` (Name defaults // to "X-API-Key"). AuthInjectAPIKey AuthInjectionType = "api_key_header" // AuthInjectPathPrefix prepends `/{token}` to the URL path. Used by // APIs that carry credentials in the path (e.g. Telegram bot API). AuthInjectPathPrefix AuthInjectionType = "path_prefix" // AuthInjectQueryParam appends `?{Name}={token}` (or merges into existing // query string). Name defaults to "token". Used by MCP servers and APIs // that auth via URL query strings. AuthInjectQueryParam AuthInjectionType = "query_param" )
type AuthRequiredError ¶
type AuthRequiredError struct {
Slug string `json:"slug"`
ConnName string `json:"connName"`
AuthURL string `json:"authUrl"`
}
AuthRequiredError is returned by ConnectionHandle.Request when a connection needs authorization.
func IsAuthRequired ¶
func IsAuthRequired(err error) (*AuthRequiredError, bool)
IsAuthRequired checks whether err is an *AuthRequiredError.
func (*AuthRequiredError) Error ¶
func (e *AuthRequiredError) Error() string
type Caller ¶ added in v0.2.0
type Caller struct {
Access Access
UserID string // optional, for audit
RunID string // optional, for audit
}
Caller carries the access level of whoever triggered the current dispatch. Framework dispatch sites (tool Execute, VM bindings, cron, webhook, route, subdomain proxy) inject one onto ctx via WithCaller. Builder Go code that constructs paths itself does NOT need to set a caller — it calls the trusted file API directly (OpenFile/ReadFile/ WriteFile/StatFile/ListDir/DeleteFile) which bypasses CheckFileAccess.
func CallerFrom ¶ added in v0.2.0
CallerFrom returns the Caller attached to ctx, defaulting to AccessPublic when none is set. This is the fail-closed default: forgetting to tag ctx denies access to anything user-or-above.
type Capabilities ¶ added in v0.3.0
type Capabilities struct {
Vision bool `json:"vision,omitempty"` // chat with images — analyzeImage / multimodal attachToContext
Transcription bool `json:"transcription,omitempty"` // speech-to-text — voice-note auto-transcribe + transcribe()
Speech bool `json:"speech,omitempty"` // text-to-speech — speech()
Embedding bool `json:"embedding,omitempty"` // vector embeddings — embed()
Image bool `json:"image,omitempty"` // image generation — generateImage()
Search bool `json:"search,omitempty"` // web search — webSearch()
}
Capabilities is a one-bool-per-slot capability matrix. Field names mirror ModelCapability constants (Vision/Transcription/Speech/ Embedding/Image) with one extra for the web-search service slot, which is a non-LLM service but follows the same agent-override → system-default resolution pattern.
type Config ¶
type Config struct {
Description string // required — shown to users in the Airlock UI
// Emoji is an optional decorative glyph shown next to the agent in
// the Airlock UI (agent list, sidebar, header). Purely cosmetic;
// empty means "no emoji". A short grapheme is expected (a single
// emoji incl. ZWJ / skin-tone / flag sequences) — it is NOT
// validated to one rune; over-long/garbage values are dropped
// server-side rather than failing the sync.
Emoji string
}
Config holds configuration for creating an Agent.
type Connection ¶
type Connection struct {
Slug string // unique per agent; binds as conn_{slug} in run_js
Name string
Description string
BaseURL string
AuthMode ConnectionAuth
AuthURL string
TokenURL string
Scopes []string
// AuthParams are extra query parameters added to the OAuth
// authorization request, overriding the platform defaults per key.
// Optional escape hatch for providers whose refresh-token handshake
// differs from the default.
AuthParams map[string]string
// Headers are static request headers Airlock sets on every proxied
// call for this connection (User-Agent, Accept, X-Foo, …). Merged
// per-key on top of the platform baseline (a real-browser UA); the
// caller's per-call ProxyRequest.Headers merge on top in turn. Set a
// value to the empty string to drop a baseline key entirely.
Headers map[string]string
AuthInjection AuthInjection
SetupInstructions string
LLMHint string // appended to the connection block in the system prompt
Access Access // who may invoke conn_{slug}; default AccessUser
}
Connection is the self-contained declaration registered via agent.RegisterConnection — an outgoing service Airlock proxies for the agent with credentials it manages.
type ConnectionAuth ¶
type ConnectionAuth string
ConnectionAuth enumerates the supported authentication strategies for an outgoing service Connection.
const ( ConnectionAuthOAuth ConnectionAuth = "oauth" ConnectionAuthToken ConnectionAuth = "token" ConnectionAuthNone ConnectionAuth = "none" )
type ConnectionDef ¶
type ConnectionDef struct {
Slug string `json:"slug,omitempty"`
Name string `json:"name"`
Description string `json:"description"`
BaseURL string `json:"baseUrl,omitempty"`
AuthMode ConnectionAuth `json:"authMode"`
AuthURL string `json:"authUrl,omitempty"`
TokenURL string `json:"tokenUrl,omitempty"`
Scopes []string `json:"scopes,omitempty"`
AuthParams map[string]string `json:"authParams,omitempty"`
Headers map[string]string `json:"headers,omitempty"`
AuthInjection AuthInjection `json:"authInjection"`
SetupInstructions string `json:"setupInstructions,omitempty"`
LLMHint string `json:"llmHint,omitempty"`
Access Access `json:"access,omitempty"`
}
ConnectionDef is the wire format used by PUT /api/agent/connections/{slug}. Slug is sent in the URL, not the body.
type ConnectionHandle ¶
type ConnectionHandle struct {
// contains filtered or unexported fields
}
ConnectionHandle is a compile-time binding to a registered connection. Returned by RegisterConnection, used to make proxied HTTP requests.
func (*ConnectionHandle) Request ¶
func (h *ConnectionHandle) Request(ctx context.Context, opts RequestOpts) ([]byte, error)
Request sends an HTTP request through Airlock's credential-injecting proxy and returns the raw response body. See RequestOpts for the call shape and field semantics.
Returns *AuthRequiredError if the connection needs authorization. The response body is buffered into memory and capped at MaxBufferedResponseBytes (20 MiB); overflow returns ErrOutputTooLarge. For larger responses, use RequestStream and pipe straight into storage.
The response body may be empty (e.g. HTTP 204 No Content, which some upstreams use for "nothing to report"). Callers passing the result to json.Unmarshal must guard `len(raw) > 0` first — otherwise stdlib returns "unexpected end of JSON input". Use RequestJSON to skip that boilerplate.
func (*ConnectionHandle) RequestStream ¶ added in v0.3.0
func (h *ConnectionHandle) RequestStream(ctx context.Context, opts RequestOpts) (*ConnectionResponse, error)
RequestStream is the streaming primitive returned to Go-only callers that want to process or persist a response without holding the full body in agent RAM. Use it for downloads, large API responses, anything you'd otherwise pipe through io.Copy:
resp, err := h.RequestStream(ctx, agentsdk.RequestOpts{Path: "/large.json"})
if err != nil { return err }
defer resp.Body.Close()
info, _ := agent.WriteFile(ctx, "tmp/large.json", resp.Body, "application/json")
402 surfaces as *AuthRequiredError; any other non-2xx becomes an opaque error carrying the upstream status and body preview. The returned Body is the live HTTP response body — close it when done.
type ConnectionResponse ¶ added in v0.3.0
type ConnectionResponse struct {
StatusCode int
Headers http.Header
Body io.ReadCloser
}
ConnectionResponse is the streaming primitive returned by ConnectionHandle.RequestStream. Body is the upstream response body, streamed through airlock's proxy with no airlock-side buffering. Caller owns the lifetime — defer Body.Close() once you've finished reading.
StatusCode and Headers carry the upstream values verbatim; airlock removes only its own auth-injection headers. A 2xx from upstream comes through as a 2xx here; auth-required surfaces as *AuthRequiredError on the parent Request* call (not via this struct).
type CreateRunRequest ¶
type CreateRunRequest struct {
TriggerType string `json:"triggerType"`
TriggerRef string `json:"triggerRef"`
}
CreateRunRequest is the body for POST /api/agent/run/create.
type CreateRunResponse ¶
type CreateRunResponse struct {
RunID string `json:"runId"`
}
CreateRunResponse is the response from POST /api/agent/run/create.
type Cron ¶
type Cron struct {
Slug string // unique per agent (across crons + schedules)
Schedule string // standard cron expression, e.g. "0 9 * * *"
Handler ScheduleHandlerFunc // required
Timeout time.Duration // max execution time (default: 2 min)
Description string
}
Cron is a recurring, code-declared schedule registered via agent.RegisterCron. It fires by schedule, never by user action — no Access field. The slug shares one namespace with RegisterSchedule (unique per agent).
type DirPath ¶ added in v0.3.0
type DirPath string
DirPath is a string-typed alias for storage directory paths. Use it when a tool argument or return value names a directory rather than a single file. Inside the same process it behaves as a plain string.
Across MCP boundaries DirPath args/results are rejected with a clear JSON-RPC error — copying directory trees is unbounded and not supported. Authors wanting cross-boundary directory semantics should restructure as []FilePath so the caller picks exact files.
type Directory ¶ added in v0.2.0
type Directory struct {
Path string // S3-style path with no leading '/', e.g. "reports"; no '..' or '//'; no trailing slash
Read Access // gates ReadFile / OpenFile / StatFile + the public read route
Write Access // gates WriteFile / DeleteFile + the public write route
List Access // gates ListDir
Description string // shown in the system prompt's directories section
// LLMHint is optional guidance shown to the LLM in the system prompt
// alongside the directory entry, e.g. "internal cache; avoid listing
// or modifying" or "user-uploaded reports; prefer summarizing over
// quoting". Authorization stays with Read/Write/List — LLMHint only
// steers the model. Empty by default.
LLMHint string
// RetentionHours, when > 0, opts the directory into Airlock's storage
// sweeper: any file in the S3 prefix older than this many hours is
// deleted on the next sweep tick (~6h cadence). Zero means files
// stay forever — that's the default for normal builder directories.
// The framework's /tmp registers with 72 to garbage-collect chat
// uploads and generated media; tools that produce throwaway artifacts
// (e.g. AI-generated images served via fileShareURL with a 1h URL
// expiry) should set a matching short TTL so the bytes go away when
// the URL does.
RetentionHours int
// Scope opts the directory into per-context isolation: WriteFile
// transparently inserts a scope segment (user-<id>/conv-<id>/run-<id>)
// between the directory prefix and the rest of the path, and reads
// only succeed when the scope key in the path matches one the
// current run owns. Use it for directories accessible to lower-trust
// callers (public-MCP, anon) where you need per-caller isolation
// without sacrificing usability — the LLM sees the scoped path,
// passes it around, and access just works for the caller who wrote
// it. Default ScopeNone preserves today's behaviour.
Scope DirectoryScope
}
Directory is the self-contained declaration registered via agent.RegisterDirectory. Each directory owns an S3 prefix ("agents/{agentID}/{Path}") and gates access through three independent caps.
The framework auto-registers a reserved directory "tmp" at Read=Write=List=AccessUser; builder calls with Path="tmp" silently keep the framework's caps (Description may still be supplied).
Read, Write, and List are independent. delete folds into Write (write on the parent governs unlink), so DeleteFile requires Write access.
type DirectoryDef ¶ added in v0.2.0
type DirectoryDef struct {
Path string `json:"path"`
Read Access `json:"read"`
Write Access `json:"write"`
List Access `json:"list"`
Description string `json:"description"`
LLMHint string `json:"llmHint,omitempty"`
RetentionHours int `json:"retentionHours,omitempty"`
Scope DirectoryScope `json:"scope,omitempty"`
}
DirectoryDef is the wire format sent in SyncRequest.
type DirectoryOpts ¶ added in v0.2.0
type DirectoryOpts struct {
Read Access // default AccessUser
Write Access // default AccessUser
List Access // default AccessUser
Description string
// LLMHint: see Directory.LLMHint. Optional model-facing guidance.
LLMHint string
// RetentionHours: see Directory.RetentionHours. Zero = no sweep.
RetentionHours int
// Scope: see Directory.Scope. Default ScopeNone (no scoping).
Scope DirectoryScope
}
DirectoryOpts is the option struct accepted by RegisterDirectory.
type DirectoryScope ¶ added in v0.3.0
type DirectoryScope string
DirectoryScope opts a directory into per-context path scoping. See Directory.Scope. Empty string ("" / ScopeNone) keeps the legacy unscoped behaviour: base ACL is the only access gate.
The three values map to the three identities a run is naturally anchored against: the calling user, the current conversation, and this single call. WriteFile picks the strongest available key from the run when scoping a path (user → conv → run); CheckFileAccess accepts any of the three on read, so a path written at user-scope remains readable from any run serving the same user.
const ( ScopeNone DirectoryScope = "" ScopeRun DirectoryScope = "run" ScopeConv DirectoryScope = "conv" ScopeUser DirectoryScope = "user" )
type DisplayPart ¶
type DisplayPart struct {
Type string `json:"type"` // "text", "image", "file", "audio", "video"
Text string `json:"text,omitempty"` // body text, or caption for media types
Source string `json:"source,omitempty"` // S3 key
URL string `json:"url,omitempty"` // external URL
Data []byte `json:"data,omitempty"` // raw bytes (base64 in JSON)
Filename string `json:"filename,omitempty"`
MimeType string `json:"mimeType,omitempty"`
Alt string `json:"alt,omitempty"` // accessibility text for images
Duration float64 `json:"duration,omitempty"` // seconds, audio/video
}
DisplayPart is a single piece of rich content for user-facing output. The `output` JS binding accepts media-only parts (image/file/audio/video); TopicHandle.Publish accepts text too, since Go builder code has no separate prose channel to use instead.
type EnvVar ¶ added in v0.2.3
type EnvVar struct {
// Slug is the unique identifier per agent. Mirrored as the URL
// segment in /api/v1/agents/{id}/env-vars/{slug}.
Slug string
// Description is shown to the operator in the editor UI. Never sent
// to the LLM.
Description string
// Secret toggles the write-only UI affordance + redaction. See the
// type doc for full semantics.
Secret bool
// Default is the value used when the operator hasn't configured the
// slot. Lets an agent ship with sensible plain-config defaults
// (region="us-east-1", timeout="30s") that the operator only
// overrides when needed.
//
// Forbidden for Secret=true: there is no sensible default for a
// credential, and a hardcoded one in agent source would defeat the
// point of the secrets surface. RegisterEnvVar panics if both are
// set.
Default string
// Pattern is an optional Go regex (RE2) the operator-supplied value
// must match. Airlock rejects values that don't match at save time,
// so typos in known-shape credentials (AWS keys, region codes,
// hostnames) surface immediately rather than at first runtime use.
// Empty string disables validation.
//
// Validated against agent's declaration (mirrors the Description
// and Default fields), not against a per-set choice — operators
// can't bypass the pattern.
Pattern string
}
EnvVar declares an operator-configured environment variable the agent will read at runtime. Operators set the value via Airlock's UI; the agent fetches it through the returned EnvVarHandle.
Two flavours, distinguished by the Secret flag:
- Secret=false: plain config value (regions, hostnames, feature flags). Operator sees and edits the current value in the UI. Not added to the agent's redact set.
- Secret=true: credential. Operator can paste a value but cannot read it back — only rotate. Auto-added to the redact set on first Get() so substring matches are stripped from LLM input.
Bytes by convention: base64-encode and decode in agent code. Single string per slug — for compound credentials register multiple slugs.
type EnvVarDef ¶ added in v0.2.3
type EnvVarDef struct {
Slug string `json:"slug,omitempty"`
Description string `json:"description"`
Secret bool `json:"secret"`
Default string `json:"default,omitempty"`
Pattern string `json:"pattern,omitempty"`
}
EnvVarDef is the wire format used by PUT /api/agent/env-vars/{slug} and (with Slug populated) by SyncRequest.EnvVars. Mirrors the agentsdk.EnvVar struct one-to-one.
type EnvVarHandle ¶ added in v0.2.3
type EnvVarHandle struct {
// contains filtered or unexported fields
}
EnvVarHandle is a compile-time binding to a registered EnvVar. Returned by RegisterEnvVar; the agent calls Get(ctx) at runtime to fetch the operator-supplied value. Values are cached on the handle for the lifetime of the agent process — call Refresh() to force a re-fetch (e.g. after the operator rotates the value).
func (*EnvVarHandle) Get ¶ added in v0.2.3
func (h *EnvVarHandle) Get(ctx context.Context) (string, error)
Get returns the operator-supplied value, falling back to the agent-declared Default when the operator hasn't set anything (always "" for secrets). For Secret=true vars, the value is registered with the agent's redact set on each fetch so it's stripped from outbound LLM input.
Return shape:
- (s, nil) — the stored value (or Default if no value was set, or "" if neither). Empty string IS a valid successful return when no Pattern is declared.
- ("", non-nil) — transport / decrypt error, or the value does not match the declared Pattern. Pattern is checked unconditionally, including against empty strings — declare Pattern="^.+$" to enforce non-empty, or any tighter regex for a known shape. Operators are blocked from saving a non-matching value at the UI, so a mismatch here usually means nothing has been configured yet (or the Pattern was tightened after a save).
Subsequent calls return the cached value until Refresh() is invoked.
func (*EnvVarHandle) IsSecret ¶ added in v0.2.3
func (h *EnvVarHandle) IsSecret() bool
IsSecret reports whether this var was registered as a secret.
func (*EnvVarHandle) Refresh ¶ added in v0.2.3
func (h *EnvVarHandle) Refresh()
Refresh discards the cached value so the next Get() re-fetches. Useful when the operator has rotated the value mid-run.
func (*EnvVarHandle) Slug ¶ added in v0.2.3
func (h *EnvVarHandle) Slug() string
Slug returns the registered slug for this handle.
type EnvVarValueResponse ¶ added in v0.3.0
type EnvVarValueResponse struct {
Value string `json:"value"`
}
EnvVarValueResponse is the wire body of GET /api/agent/env-vars/{slug} — the operator-supplied value for one declared env var (or 404 if no value is configured).
type EventWriter ¶
type EventWriter struct {
// contains filtered or unexported fields
}
EventWriter streams NDJSON events to an HTTP response.
func (*EventWriter) WriteError ¶
func (ew *EventWriter) WriteError(err error) error
WriteError writes an error event.
func (*EventWriter) WriteEvent ¶
func (ew *EventWriter) WriteEvent(event stream.Event) error
WriteEvent serializes a GoAI stream.Event as an NDJSON line.
func (*EventWriter) WriteProgress ¶
func (ew *EventWriter) WriteProgress(message string) error
WriteProgress writes a progress event (for webhook/cron handlers).
type ExecCommand ¶ added in v0.3.0
type ExecCommand struct {
Command string `json:"command"`
Args []string `json:"args,omitempty"`
Stdin []byte `json:"-"` // marshalled separately as base64
Timeout time.Duration `json:"-"` // 0 = server default (60s); marshalled as timeoutMs
}
ExecCommand is the input to ExecHandle.Run / ExecHandle.RunStream.
Command is handed to the remote shell as a single command line: pipes, redirection, and shell substitution in Command just work because the remote sshd execs the user's login shell with it. Args are POSIX-shell-quoted and space-joined onto Command before send, so Run("ls", []string{"-la", "my dir"}) sends `ls -la 'my dir'` safely.
Use Args for safe multi-arg commands; put any shell features (pipes, redirection) in Command and leave Args empty.
type ExecEndpoint ¶ added in v0.3.0
type ExecEndpoint struct {
Slug string // unique per agent; binds as exec_{slug} in run_js
Description string
LLMHint string // appended to the endpoint block in the system prompt
Access Access // who may invoke; default AccessAdmin; AccessPublic is silently demoted to AccessUser
}
ExecEndpoint is the self-contained declaration registered via agent.RegisterExecEndpoint — a remote target airlock executes commands against on the agent's behalf. The transport (ssh today; telnet, endpoint-binary later) and credentials are operator-configured via the Airlock UI; the agent's main() only declares slug + description + access.
type ExecEndpointDef ¶ added in v0.3.0
type ExecEndpointDef struct {
Slug string `json:"slug,omitempty"`
Description string `json:"description"`
LLMHint string `json:"llmHint,omitempty"`
Access Access `json:"access,omitempty"`
}
ExecEndpointDef is the wire format used by PUT /api/agent/exec-endpoints/{slug}. Slug travels in the URL. Operator-configured fields stay airlock-side and are not present here — the agent only declares its intent to use the slug.
type ExecError ¶ added in v0.3.0
type ExecError struct {
Kind string // "transport" | "timeout" | "config" | "denied"
Message string
}
ExecError distinguishes transport-class problems (the command never ran) from runtime failures (the command ran and reported a non-zero exit code, which is just an ExecResult with a non-zero ExitCode).
type ExecExit ¶ added in v0.3.0
ExecExit is the terminal status of a streaming exec call. Returned by ExecStream.Wait once the remote has closed both stdout and stderr.
type ExecHandle ¶ added in v0.3.0
type ExecHandle struct {
// contains filtered or unexported fields
}
ExecHandle is the compile-time-bound handle returned by RegisterExecEndpoint. Use Run for buffered convenience (capped at MaxBufferedResponseBytes, surfaces ErrOutputTooLarge on overflow); use RunStream when the output is data you want to pipe straight into storage without holding it in agent RAM.
func (*ExecHandle) Run ¶ added in v0.3.0
func (h *ExecHandle) Run(ctx context.Context, cmd ExecCommand) (ExecResult, error)
Run is the buffered convenience wrapper around RunStream. It reads both streams up to MaxBufferedResponseBytes each, then waits for the exit envelope. Overflow on either stream returns ErrOutputTooLarge with no partial ExecResult — use RunStream and pipe straight into agent.WriteFile if you expect larger output.
Non-zero exit codes are NOT errors — inspect ExecResult.ExitCode and ExecResult.Stderr. Errors returned by Run are *ExecError for transport failures, ErrOutputTooLarge for buffer overflow, or context errors.
func (*ExecHandle) RunStream ¶ added in v0.3.0
func (h *ExecHandle) RunStream(ctx context.Context, cmd ExecCommand) (*ExecStream, error)
RunStream opens an exec session over the agent's airlock client and returns reader handles for stdout/stderr plus a Wait function that blocks until the remote sends its exit envelope.
The caller MUST close both Stdout and Stderr even if it only consumes one — the background demux goroutine drives both, and a half-drained stream blocks the other side. The simplest idiom is:
s, err := h.RunStream(ctx, cmd)
if err != nil { return err }
defer s.Stdout.Close()
defer s.Stderr.Close()
func (*ExecHandle) Slug ¶ added in v0.3.0
func (h *ExecHandle) Slug() string
Slug returns the endpoint's registered slug. Useful for log lines and error wrapping when the caller has a generic *ExecHandle.
type ExecRequest ¶ added in v0.3.0
type ExecRequest struct {
Command string `json:"command"`
Args []string `json:"args,omitempty"`
StdinB64 string `json:"stdinB64,omitempty"`
TimeoutMs int64 `json:"timeoutMs,omitempty"`
}
ExecRequest is the wire body of POST /api/agent/exec/{slug}. Stdin arrives base64-encoded because it can be raw bytes and JSON can't carry those directly. TimeoutMs of 0 means "use the server default".
type ExecResult ¶ added in v0.3.0
ExecResult is what Run returns when the call fits in the 20 MiB buffer cap. Overflow returns ErrOutputTooLarge with no partial result — use RunStream for outputs that may exceed the cap.
type ExecStream ¶ added in v0.3.0
type ExecStream struct {
Stdout io.ReadCloser
Stderr io.ReadCloser
Wait func() (ExecExit, error)
}
ExecStream is the streaming primitive returned by ExecHandle.RunStream. Mirrors os/exec.Cmd's StdoutPipe / StderrPipe / Wait shape so Go users get a familiar mental model:
s, _ := vps.RunStream(ctx, ExecCommand{Command: "tar -czf - /var/log"})
defer s.Stdout.Close()
defer s.Stderr.Close()
info, _ := agent.WriteFile(ctx, "tmp/logs.tar.gz", s.Stdout, "application/gzip")
exit, _ := s.Wait()
Stdout and Stderr stay open until the remote closes its side; Wait blocks until the exit envelope arrives. Always close both pipes — even when you only care about one — to release the demux goroutines.
type FileInfo ¶ added in v0.2.0
type FileInfo struct {
Path FilePath `json:"path"` // S3-style storage path, e.g. "uploads/foo.png"
Filename string `json:"filename"` // original upload name; S3 metadata
ContentType string `json:"contentType"`
Size int64 `json:"size"`
LastModified time.Time `json:"lastModified"`
}
FileInfo describes a file in agent storage. Returned by StatFile, ListDir, WriteFile, and embedded in PromptInput.Files for chat uploads. Path is the canonical identifier; Filename is the original upload name preserved as S3 metadata so the LLM can refer to "Q1 Report.pdf" while the path uses a uuid-prefixed safe filename.
type FileOp ¶ added in v0.2.0
type FileOp string
FileOp tags an operation passed to CheckFileAccess. Delete folds into OpWrite (write on the parent governs unlink); there is no separate OpDelete.
type FilePath ¶ added in v0.3.0
type FilePath string
FilePath is a string-typed alias for storage paths owned by this agent. Use it for tool input/output fields that name a file the tool reads, writes, returns, or consumes. Inside the same process (run_js, Go code) it behaves as a plain string.
At MCP boundaries airlock rewrites the path so callees always see one readable in their own bucket: cross-bucket copy for A2A, base64 materialization for external MCP clients. Authors don't need to think about this — declaring `FilePath` is the entire opt-in.
type HTTPRequest ¶
type HTTPRequest struct {
URL string `json:"url"`
Method string `json:"method,omitempty"` // default: GET
Headers map[string]string `json:"headers,omitempty"`
Body string `json:"body,omitempty"`
Timeout int `json:"timeout,omitempty"` // seconds, default: 30, max: 120
SaveAs string `json:"saveAs,omitempty"` // save response body to S3 at this key (binary-safe)
Raw bool `json:"raw,omitempty"` // skip HTML→markdown conversion for HTML responses
// AllHeaders returns every upstream response header. Default (false)
// returns only the curated few an agent reasons about; the rest
// (CSP, Via, Alt-Svc, telemetry) are noise that burns context.
AllHeaders bool `json:"allHeaders,omitempty"`
}
HTTPRequest is the body for POST /api/agent/http.
type HTTPResponse ¶
type HTTPResponse struct {
Status int `json:"status"`
Headers map[string]string `json:"headers"`
Body string `json:"body,omitempty"`
ContentType string `json:"contentType"` // original upstream Content-Type
// Size is the byte length of the content the agent can act on — the
// inline body, the converted markdown, or the object written to
// SavedTo. Always populated (never the upstream Content-Length,
// which is 0 for chunked/unknown).
Size int `json:"size"`
// BodyPreview is the head (~1 KB) of a saved text/markdown body so
// the result is legible without a second fileRead. Empty for binary
// or inline (Body carries the whole thing) responses.
BodyPreview string `json:"bodyPreview,omitempty"`
SavedTo string `json:"savedTo,omitempty"` // S3 key if body was auto-saved
Note string `json:"note,omitempty"` // human-readable note about transformations applied (e.g. HTML→markdown conversion)
}
HTTPResponse is returned from POST /api/agent/http.
type HTTPSessionStore ¶
type HTTPSessionStore struct {
// contains filtered or unexported fields
}
HTTPSessionStore implements session.SessionStore by calling Airlock's API. It is pre-scoped to a single conversation at construction time.
func NewHTTPSessionStore ¶
func NewHTTPSessionStore(client *airlockClient, convID, runID, source string) *HTTPSessionStore
NewHTTPSessionStore creates a store scoped to the given conversation.
type Instruction ¶
Instruction is the self-contained declaration passed to agent.AddInstruction. The Text fragment is appended to the system prompt for runs whose caller access matches one of the listed Access levels. Empty Access slice means "applies to every access level."
type InstructionDef ¶
InstructionDef is the wire format sent in SyncRequest.
type LLMProxyRequest ¶
type LLMProxyRequest struct {
Slug string `json:"slug,omitempty"`
Capability string `json:"capability,omitempty"`
Options json.RawMessage `json:"options"`
}
LLMProxyRequest is the body for POST /api/agent/llm/stream.
type ListOpts ¶ added in v0.2.0
type ListOpts struct {
// Recursive walks the entire subtree. Zero value (false) lists only
// files directly under the path (one level only, like `ls`).
Recursive bool
}
ListOpts controls ListDir.
type ListSchedulesFilter ¶
type ListSchedulesFilter struct {
Slug string
}
ListSchedulesFilter narrows ListSchedules. An empty Slug lists every pending fire for the agent.
type LogEntry ¶
LogEntry is one builder-emitted line: a level and a message. The wire format used by /api/agent/run/complete; also the in-memory shape on the run.
type LogLevel ¶
type LogLevel string
LogLevel categorizes a builder-emitted log line. UI can color/filter on it; the wire format stores it explicitly so the level isn't lost in a flat string.
type MCP ¶
type MCP struct {
Slug string // unique per agent; binds as mcp_{slug} in run_js
Name string
URL string
AuthMode MCPAuth
AuthURL string
TokenURL string
Scopes []string
// AuthInjection picks how the stored credential is added to each MCP
// HTTP call: bearer header (default), custom header, query parameter,
// or path prefix. Mirrors Connection.AuthInjection.
AuthInjection AuthInjection
Access Access // who may invoke mcp_{slug}; default AccessUser
}
MCP is the self-contained declaration registered via agent.RegisterMCP. Slug binds as mcp_{slug} in run_js; the builder uses the returned *MCPHandle to call tools from Go.
type MCPAuth ¶
type MCPAuth string
MCPAuth enumerates the supported authentication strategies for an MCP server. MCPAuthOAuthDiscovery is MCP-specific (RFC 9728 server-advertised OAuth endpoints) and not available on Connection.
type MCPAuthStatus ¶
type MCPAuthStatus struct {
Slug string `json:"slug"`
AuthMode MCPAuth `json:"authMode"`
Authorized bool `json:"authorized"`
AuthURL string `json:"authUrl,omitempty"`
// Instructions is the server-level description the remote MCP server
// advertised in its initialize result (the spec's `instructions`
// field). Empty when the server set none. Rendered next to
// mcp_<slug> in the prompt so the model knows what the server is for.
Instructions string `json:"instructions,omitempty"`
}
MCPAuthStatus reports auth state for an MCP server.
type MCPContent ¶
type MCPContent struct {
Type string `json:"type"`
Text string `json:"text,omitempty"`
URI string `json:"uri,omitempty"`
Name string `json:"name,omitempty"`
MimeType string `json:"mimeType,omitempty"`
Data string `json:"data,omitempty"`
}
MCPContent is a single content block in an MCP tool response. MCP defines five content types; we keep the fields we surface to JS callers. URI is set for resource_link; Data + MimeType for image/audio; Name for resource_link display.
type MCPDef ¶
type MCPDef struct {
Slug string `json:"slug,omitempty"`
Name string `json:"name"`
URL string `json:"url"`
AuthMode MCPAuth `json:"authMode"`
AuthURL string `json:"authUrl,omitempty"`
TokenURL string `json:"tokenUrl,omitempty"`
Scopes []string `json:"scopes,omitempty"`
AuthInjection AuthInjection `json:"authInjection"`
Access Access `json:"access,omitempty"`
}
MCPDef is the wire format used by PUT /api/agent/mcp-servers/{slug} and (with Slug populated) by SyncRequest.MCPServers. Slug is sent in the URL for the per-slug PUT and in the body for the bulk sync.
type MCPHandle ¶
type MCPHandle struct {
// contains filtered or unexported fields
}
MCPHandle is a compile-time binding to a registered MCP server. Returned by RegisterMCP, used to call tools and build tool sets.
func (*MCPHandle) CallTool ¶
func (h *MCPHandle) CallTool(ctx context.Context, toolName string, args any) (*MCPToolCallResponse, error)
CallTool calls a tool on this MCP server via Airlock's proxy. Args encoding mirrors ConnectionHandle.Request:
nil — sent as {} (MCP requires a JSON object)
[]byte, string, json.RawMessage — assumed to be valid JSON, sent as-is
io.Reader — fully read, assumed JSON, sent as-is
anything else — JSON-marshalled
type MCPToolCallRequest ¶
type MCPToolCallRequest struct {
Tool string `json:"tool"`
Arguments json.RawMessage `json:"arguments"`
}
MCPToolCallRequest is the body for POST /api/agent/mcp/{slug}/tools/call.
type MCPToolCallResponse ¶
type MCPToolCallResponse struct {
Content []MCPContent `json:"content"`
IsError bool `json:"isError"`
}
MCPToolCallResponse is returned from MCP tool call proxy.
type MCPToolSchema ¶
type MCPToolSchema struct {
ServerSlug string `json:"serverSlug"`
Name string `json:"name"`
Description string `json:"description"`
InputSchema json.RawMessage `json:"inputSchema"`
}
MCPToolSchema is a discovered MCP tool schema returned in SyncResponse.
type MockAirlock ¶
type MockAirlock struct {
Server *httptest.Server
// LLMResponse is the NDJSON response returned by POST /api/agent/llm/stream.
// Set this before making prompt requests.
LLMResponse []byte
// contains filtered or unexported fields
}
MockAirlock is an httptest server that implements the Airlock agent API. Use NewMockAirlock() to create one. Exported for agent developers' tests.
func NewMockAirlock ¶
func NewMockAirlock() (*MockAirlock, string)
NewMockAirlock creates a mock Airlock server and returns it along with the base URL.
func (*MockAirlock) Requests ¶
func (m *MockAirlock) Requests() []MockRequest
Requests returns all recorded requests.
func (*MockAirlock) RequestsByPath ¶
func (m *MockAirlock) RequestsByPath(prefix string) []MockRequest
RequestsByPath returns requests matching the given path prefix.
type MockRequest ¶
MockRequest records a request made to the mock Airlock server.
type ModelCapability ¶
type ModelCapability string
ModelCapability describes what kind of model is needed.
const ( CapText ModelCapability = "text" // any chat/language model CapVision ModelCapability = "vision" // chat model that accepts images CapEmbedding ModelCapability = "embedding" // vector embeddings CapImage ModelCapability = "image" // image generation CapSpeech ModelCapability = "speech" // text-to-speech CapTranscription ModelCapability = "transcription" // speech-to-text CapSearch ModelCapability = "search" // web search provider (provider-bound, optional model) )
type ModelProxyRequest ¶
type ModelProxyRequest struct {
Slug string `json:"slug,omitempty"`
Capability string `json:"capability"`
Options json.RawMessage `json:"options"`
}
ModelProxyRequest is the body for non-streaming model endpoints (POST /api/agent/llm/{image,embedding,speech,transcription}).
type ModelSlot ¶
type ModelSlot struct {
Slug string
Capability ModelCapability // required: CapText, CapVision, CapImage, CapSpeech, CapTranscription, CapEmbedding
Description string // human-readable hint shown in the admin UI
}
ModelSlot is the self-contained declaration registered via agent.RegisterModel.
type ModelSlotDef ¶
type ModelSlotDef struct {
Slug string `json:"slug"`
Capability string `json:"capability"`
Description string `json:"description,omitempty"`
}
ModelSlotDef is the wire format sent in SyncRequest. The agent uses Slug at runtime (e.g. `agent.LLM(ctx, slug, ...)`); the admin binds a specific model to the slug in the Airlock UI. When no model is bound, calls fall through to the agent's per-capability default and then to the system default for that capability.
type PrintRequest ¶
type PrintRequest struct {
Parts []DisplayPart `json:"parts"`
Topic string `json:"topic,omitempty"` // empty = direct to conversation
ConversationID string `json:"conversationId,omitempty"` // set for direct prints
RunID string `json:"runId,omitempty"` // originating run, used to sort ephemerals after their run's assistant messages
UserID string `json:"userId,omitempty"` // topic publish scoped to this user's subscribed conversations (PublishToUser)
}
PrintRequest is the body for POST /api/agent/print.
type PromptData ¶ added in v0.3.0
type PromptData struct {
// AgentDashboardURL points at the agent's settings page in the
// Airlock UI; the prompt tells the LLM to direct users there when
// a connection or MCP server needs OAuth.
AgentDashboardURL string `json:"agentDashboardUrl"`
// AgentRouteURL is the agent's public subdomain (scheme + host +
// optional port). The prompt embeds it for "share file at this
// URL" guidance. Derived server-side because the scheme/port
// logic lives in airlock's PUBLIC_URL parsing.
AgentRouteURL string `json:"agentRouteUrl"`
// Siblings is the FULL configured sibling list with each one's
// tool schemas. Static at sync time (changes when the operator
// edits the address book). Per-user visibility is layered on at
// dispatch via PromptInput.VisibleSiblings.
Siblings []SiblingInfo `json:"siblings,omitempty"`
// Capabilities are the model slots Airlock has bound for this
// agent (agent override → system default). Each bool is true iff
// some model is bound for that slot — the prompt branches on
// these to avoid recommending builtins that would 4xx at
// runtime (e.g. analyzeImage on an agent with no vision model).
Capabilities Capabilities `json:"capabilities,omitempty"`
// SupportedModalities is the exec model's declared input modality
// list ("text", "image", "pdf", "audio", "video"). Airlock resolves
// it from the agent's configured model and keeps it current: an
// immediate /refresh when the model changes, plus a dispatch-time
// hash check that resyncs a stale cache before the run. Every run
// path (web, bridge, webhook, cron, A2A) renders from this one value
// — both the prompt template and the attachToContext modality guard.
SupportedModalities []string `json:"supportedModalities,omitempty"`
}
PromptData is the platform-supplied slice of the prompt-render input — everything the agent can't compute locally from its own in-memory registrations.
type PromptInput ¶
type PromptInput struct {
Messages []message.Message `json:"messages"`
Message string `json:"message,omitempty"` // New user message text (used with SessionStore)
ConversationID string `json:"conversationId,omitempty"`
ProviderID string `json:"providerId,omitempty"`
ModelID string `json:"modelId,omitempty"`
Temperature *float64 `json:"temperature,omitempty"`
MaxOutputTokens *int `json:"maxOutputTokens,omitempty"`
ProviderOptions json.RawMessage `json:"providerOptions,omitempty"`
Files []FileInfo `json:"files,omitempty"`
ResumeRunID string `json:"resumeRunId,omitempty"`
Approved *bool `json:"approved,omitempty"`
Source string `json:"source,omitempty"` // "user" (default), "system" (injected by Airlock)
// ExpectedSyncHash is airlock's current SyncResponse.SyncStateHash for
// this agent, stamped on every dispatch. When it differs from the value
// the agent cached at its last sync, the agent's config cache is stale
// (a model-slot/slug change that didn't fire /refresh) — the agent logs
// a warning and resyncs before running so this turn already sees fresh
// capabilities/modalities. Empty from an older airlock (check skipped).
ExpectedSyncHash string `json:"expectedSyncHash,omitempty"`
// Instructions is an access-filtered concatenation of the agent's
// registered AddInstruction fragments, composed by Airlock at run
// dispatch. The agent appends this to its sync-cached system prompt.
Instructions string `json:"instructions,omitempty"`
// CallerAccess is the resolved per-(agent, user) access level for the
// triggering caller. agentsdk uses it to gate which conn_/mcp_/topic_/
// storage_ JS bindings (and registered tools) are exposed to the run.
// Airlock sets this from trigger.ResolveAgentAccess. For trusted server
// triggers (webhooks, crons) Airlock sends AccessAdmin.
CallerAccess Access `json:"callerAccess,omitempty"`
// VisibleSiblings are the sibling-agent IDs this run's user is
// authorized to A2A-call. UUIDs (not slugs) so a mid-run rename
// doesn't silently revoke or reassign bindings. Computed by Airlock
// at dispatch using the same access ladder that gates the MCP
// endpoint. agentsdk intersects this with the sync-cached
// PromptData.Siblings (matched on .ID) for both prompt rendering
// and VM bindings — so the prompt and the runtime agree about which
// agent_<slug> namespaces are reachable on this run.
VisibleSiblings []uuid.UUID `json:"visibleSiblings,omitempty"`
// ForceCompact tells the agent to skip the thinking loop and run a
// user-triggered compaction instead. Message is ignored when set. The
// agent loads conversation history, asks the model to summarize it,
// persists the summary via the SessionStore's Compact method, and emits
// a short text-delta describing the outcome.
ForceCompact bool `json:"forceCompact,omitempty"`
// AutoConfirm makes run_js skip the request_confirmation gate and
// execute directly. Airlock sets it for runs that have no interactive
// second turn in which to answer a confirmation — currently public
// one-shot bridge sessions. It governs only this run's own run_js; a
// suspension that still reaches the triggering surface by another path
// (e.g. an A2A-delegated confirmation) is auto-denied there instead.
AutoConfirm bool `json:"autoConfirm,omitempty"`
// DirectTools selects the per-run tool surface. When false (default),
// the LLM gets one `run_js` tool and capabilities are JS bindings inside
// the goja sandbox. When true, every capability is its own typed LLM
// tool — no JS sandbox, no TypeScript manifest in the prompt. Airlock
// sets this based on the resolved caller; today it's hardcoded to
// `callerAccess == AccessPublic`.
DirectTools bool `json:"directTools,omitempty"`
// Platform / UserDisplayName / UserEmail are per-turn context for the
// prompt's <env> block. Airlock sets Platform explicitly per dispatch
// path (web/telegram/discord/a2a — never inferred) and resolves the
// originating user's name/email; any may be empty (then omitted).
Platform string `json:"platform,omitempty"`
UserDisplayName string `json:"userDisplayName,omitempty"`
UserEmail string `json:"userEmail,omitempty"`
}
PromptInput is the request body for POST /prompt.
type ProxyRequest ¶
type ProxyRequest struct {
Method string `json:"method"`
Path string `json:"path"`
Body string `json:"body,omitempty"`
Headers map[string]string `json:"headers,omitempty"`
}
ProxyRequest is the body for POST /api/agent/proxy/{slug}.
Headers are per-call request headers, merged per-key on top of the connection's declared Headers (which themselves sit on top of the platform baseline). Set a value to the empty string to suppress a key set by a lower layer. omitempty: a call that doesn't need custom headers can simply omit the field.
type RegisterOption ¶
type RegisterOption func(*registeredTool)
RegisterOption layers agentsdk-only concerns onto a registered tool that goai's provider-agnostic tool.Tool doesn't model.
func WithLLMHint ¶
func WithLLMHint(hint string) RegisterOption
WithLLMHint adds model-only guidance appended to the tool's description in the system prompt — kept out of member-facing UIs that render the bare description (e.g. the dashboard's Tools tab).
type RequestOpts ¶ added in v0.3.0
type RequestOpts struct {
// Method is the HTTP verb. Empty defaults to "GET" (the majority
// of calls).
Method string
// Path is appended to the connection's BaseURL. Required.
Path string
// Body is encoded by type when non-nil: []byte / string sent as-is,
// io.Reader fully read, anything else JSON-marshalled.
Body any
// Headers merge per-key on top of the platform baseline (real-browser
// User-Agent) and the connection's declared Headers. Set a value to
// the empty string to suppress a key set by a lower layer. Nil/empty
// map means no overrides.
Headers map[string]string
}
RequestOpts is the call shape for ConnectionHandle.Request / RequestStream / RequestJSON. Mirrors the options-dict pattern of axios / fetch / python-requests so call sites read declaratively instead of positionally — most calls only need Path, and adding Body or Headers later is a structural edit instead of a shift of every argument.
// Simple GET (Method defaults to "GET"):
body, _ := conn.Request(ctx, agentsdk.RequestOpts{Path: "/v1/me"})
// POST with body:
conn.Request(ctx, agentsdk.RequestOpts{
Method: "POST", Path: "/v1/playlists", Body: playlist,
})
// With per-call headers:
conn.Request(ctx, agentsdk.RequestOpts{
Path: "/v1/me/player",
Headers: map[string]string{"If-None-Match": etag},
})
type Route ¶
type Route struct {
Method string // "GET", "POST", ...
Path string // e.g. "/spotify"
Handler RouteHandlerFunc // required
Access Access // required: AccessAdmin, AccessUser, or AccessPublic
Description string
}
Route is the self-contained declaration registered via agent.RegisterRoute. Custom HTTP routes served by the agent and proxied by Airlock via subdomain routing. The (Method, Path) pair must be unique per agent.
type RouteDef ¶
type RouteDef struct {
Path string `json:"path"`
Method string `json:"method"`
Access Access `json:"access"`
Description string `json:"description,omitempty"`
}
RouteDef is a custom HTTP route definition sent during sync.
type RouteHandlerFunc ¶
RouteHandlerFunc handles custom HTTP routes registered via RegisterRoute.
type RunCompleteRequest ¶
type RunCompleteRequest struct {
RunID string `json:"runId"`
// Status is "success" | "error" | "suspended" | "timeout" | "tool_errors".
Status string `json:"status"`
Error string `json:"error,omitempty"`
// ErrorKind is set when Status == "error" and disambiguates platform
// vs agent failure for the UI. Empty otherwise.
ErrorKind string `json:"errorKind,omitempty"`
PanicTrace string `json:"panicTrace,omitempty"`
Actions json.RawMessage `json:"actions"`
Logs []LogEntry `json:"logs,omitempty"`
Checkpoint json.RawMessage `json:"checkpoint,omitempty"`
}
RunCompleteRequest is the body for POST /api/agent/run/complete.
type Schedule ¶
type Schedule struct {
Slug string // unique per agent (across crons + schedules)
Handler ScheduleHandlerFunc // required
Timeout time.Duration // max execution time (default: 2 min)
Description string
}
Schedule is a code-declared handler for runtime-armed one-shot fires registered via agent.RegisterSchedule. Arm an instance with agent.ScheduleAt; per-instance data lives in the agent's own DB (keyed by the returned fire id), not in the platform. No Access field — fires are trusted/system.
type ScheduleAtRequest ¶
ScheduleAtRequest arms a one-shot fire of a registered handler. Body of POST /api/agent/schedules; the response carries the new fire id.
type ScheduleHandlerDef ¶
type ScheduleHandlerDef struct {
Slug string `json:"slug"`
Kind string `json:"kind"`
Recurrence string `json:"recurrence,omitempty"`
TimeoutMs int64 `json:"timeoutMs"`
Description string `json:"description,omitempty"`
}
ScheduleHandlerDef is one registered cron/schedule handler sent during sync. Kind is "cron" or "schedule"; Recurrence is the cron expression for crons, empty for schedules.
type ScheduleHandlerFunc ¶
type ScheduleHandlerFunc func(ctx context.Context, ew *EventWriter) error
ScheduleHandlerFunc handles a timed fire of a registered cron or schedule. It carries no payload — per-instance data lives in the agent's own DB, keyed by the fire id (see ScheduleFromContext).
type ScheduledFire ¶
type ScheduledFire struct {
ID string `json:"id"`
Slug string `json:"slug"`
Kind string `json:"kind"` // "cron" | "schedule"
FireAt time.Time `json:"fireAt"`
Status string `json:"status"` // pending|fired|error|orphaned|cancelled
Recurrence string `json:"recurrence,omitempty"`
}
ScheduledFire is one pending/recorded fire row, returned by ListSchedules.
type ScheduledFireRef ¶
ScheduledFireRef identifies the fire that triggered the current handler run. Read it with ScheduleFromContext to look up the per-instance data the agent stored in its own DB at ScheduleAt time.
func ScheduleFromContext ¶
func ScheduleFromContext(ctx context.Context) (ScheduledFireRef, bool)
ScheduleFromContext returns the fire that triggered the current handler run. The second return is false outside a /fire handler (a normal prompt/webhook run). Use FireID to look up the per-instance data stored at ScheduleAt time.
type SealRequest ¶ added in v0.3.0
type SealRequest struct {
Plaintext string `json:"plaintext"`
}
SealRequest / SealResponse are the wire bodies of POST /api/agent/seal: the agent posts plaintext it generated at runtime, airlock returns an opaque sealed blob bound to this agent's ID.
type SealResponse ¶ added in v0.3.0
type SealResponse struct {
Sealed string `json:"sealed"`
}
type SearchProxyRequest ¶
type SearchProxyRequest struct {
Slug string `json:"slug,omitempty"`
Capability string `json:"capability,omitempty"`
websearch.Request // embedded: fields promoted to the top-level JSON object
}
SearchProxyRequest is the body for POST /api/agent/search, shared with airlock (which decodes it). Slug names the registered CapSearch model slot whose bound provider Airlock should use; empty slug resolves to the agent's configured search provider (then the system default), exactly like an unbound model slot. The websearch.Request fields are embedded (promoted in JSON) so sol/websearch.Request stays provider-facing — the slug lives only on this envelope.
type SessionCompactRequest ¶ added in v0.3.0
type SessionCompactRequest struct {
Summary []session.Message `json:"summary"`
TokensFreed int `json:"tokensFreed"`
}
SessionCompactRequest is the wire body of POST /api/agent/session/{convID}/compact: the agent posts the summarized message tail it wants to keep, plus a count of tokens the summarization freed, and airlock writes a checkpoint marker row followed by the summary.
type ShareFileRequest ¶ added in v0.2.1
type ShareFileRequest struct {
}
ShareFileRequest is the body for POST /api/agent/storage/share. Path is an S3-style storage path (no leading slash); ExpiresSeconds caps how long the returned URL is valid for. Server defaults to 1h if 0, caps at 24h.
type ShareFileResponse ¶ added in v0.2.1
type ShareFileResponse struct {
}
ShareFileResponse is returned by POST /api/agent/storage/share. URL is unauthenticated and valid until ExpiresAtMs (ms epoch).
type SiblingHandle ¶ added in v0.3.0
type SiblingHandle struct {
// contains filtered or unexported fields
}
SiblingHandle invokes tools on a sibling agent via airlock's MCP server endpoint. The wire path is identical to what an external MCP client (Claude Desktop, VS Code) would use — JSON-RPC `tools/call` to /api/agent/{siblingID}/mcp — but auth is this agent's own bearer token plus an X-Run-ID header that tells airlock which caller-side run the call belongs to.
File arguments and results are translated at the airlock boundary: FilePath args declared in the sibling's tool input schema are copied cross-bucket into the sibling's __a2a/{callerRunID}/ namespace before dispatch, and FilePath results are copied back into this agent's a2a/<sibling-slug>/ namespace. The handle just speaks JSON-RPC; the translation is invisible.
func (*SiblingHandle) CallTool ¶ added in v0.3.0
func (h *SiblingHandle) CallTool(ctx context.Context, callerRunID, toolName string, args any) (any, error)
CallTool invokes a named tool on the sibling. callerRunID is the current run's ID (X-Run-ID header) — required so airlock can resolve the caller's identity for permissions + key __a2a/{runID}/ blobs.
type SiblingInfo ¶ added in v0.3.0
type SiblingInfo struct {
// ID is the canonical, rename-safe identifier. MCP outbound calls
// use the UUID in the URL path so a sibling rename doesn't break
// in-flight bindings.
ID uuid.UUID `json:"id"`
// Slug is the human-readable binding name — appears in the prompt
// and as the `agent_<slug>` namespace on this agent's VM.
Slug string `json:"slug"`
Name string `json:"name"`
Description string `json:"description,omitempty"`
Tools []MCPToolSchema `json:"tools,omitempty"`
}
SiblingInfo describes one sibling agent in the caller's address book. Travels in PromptData.Siblings.
type SyncRequest ¶
type SyncRequest struct {
Version string `json:"version"`
Description string `json:"description,omitempty"`
Emoji string `json:"emoji,omitempty"`
Tools []ToolDef `json:"tools,omitempty"`
Webhooks []WebhookDef `json:"webhooks"`
ScheduleHandlers []ScheduleHandlerDef `json:"scheduleHandlers"`
Routes []RouteDef `json:"routes,omitempty"`
Topics []TopicDef `json:"topics,omitempty"`
MCPServers []MCPDef `json:"mcpServers,omitempty"`
Connections []ConnectionDef `json:"connections,omitempty"`
ExecEndpoints []ExecEndpointDef `json:"execEndpoints,omitempty"`
EnvVars []EnvVarDef `json:"envVars,omitempty"`
Directories []DirectoryDef `json:"directories,omitempty"`
Instructions []InstructionDef `json:"instructions,omitempty"`
ModelSlots []ModelSlotDef `json:"modelSlots,omitempty"`
}
SyncRequest is the body for PUT /api/agent/sync.
type SyncResponse ¶
type SyncResponse struct {
// PromptData carries the slice of system-prompt input the agent
// can't derive locally: dashboard / route URLs, the full sibling
// address book with their published tool schemas. Required. An
// older agentsdk that doesn't know about PromptData would have
// produced an empty system prompt; the new agentsdk's
// applySyncResponse panics on a zero-value PromptData with a
// clear "your airlock is newer than your agentsdk" message.
PromptData PromptData `json:"promptData"`
MCPAuthStatus []MCPAuthStatus `json:"mcpAuthStatus,omitempty"`
// MCPSchemas carries discovered tool schemas per MCP server slug.
// Airlock populates these from its server-side discovery cache so the
// agent's VM can install one typed JS method per tool on each
// `mcp_{slug}` object — no per-run discovery round-trips.
MCPSchemas map[string][]MCPToolSchema `json:"mcpSchemas,omitempty"`
// PublicStorageBase is the URL prefix at which directories are reachable
// on the agent's subdomain, ending without a trailing slash. Callers
// join with '/' and the storage path (e.g. "reports/q1.csv") to
// construct a URL: "https://{slug}.{domain}/__air/storage/reports/q1.csv".
// The proxy enforces the directory's Read cap at fetch time — public
// dirs serve unauthenticated, user/admin dirs require subdomain login
// (redirect-on-missing-cookie).
PublicStorageBase string `json:"publicStorageBase,omitempty"`
// SyncStateHash fingerprints the airlock-authoritative agent config
// that feeds this PromptData (model slots + slug). Airlock stamps the
// same hash onto every dispatched PromptInput.ExpectedSyncHash; the
// agent caches this value and self-heals (resyncs) when a dispatch
// arrives carrying a different one — catching a config change that
// didn't fire an explicit /refresh. Opaque to the agent. Empty from an
// older airlock, in which case the agent skips the drift check.
SyncStateHash string `json:"syncStateHash,omitempty"`
}
SyncResponse is the response from PUT /api/agent/sync.
The agent renders its own system prompt per run from PromptData (platform-side data) plus its in-memory registrations. Airlock no longer ships a pre-rendered SystemPrompt: per-run rendering is the only way to express per-user sibling visibility without exploding the wire payload into N variants.
type ToolDef ¶
type ToolDef struct {
Name string `json:"name"`
Description string `json:"description"`
LLMHint string `json:"llmHint,omitempty"`
Access Access `json:"access"`
InputSchema json.RawMessage `json:"inputSchema,omitempty"`
OutputSchema json.RawMessage `json:"outputSchema,omitempty"`
InputExamples []json.RawMessage `json:"inputExamples,omitempty"`
}
ToolDef describes a registered tool sent during sync. Carries the JSON schemas for input and output so Airlock can render TypeScript signatures in the system prompt and surface them in the UI.
type Topic ¶
type Topic struct {
Slug string
Description string
LLMHint string // optional model-only guidance — see Directory.LLMHint
Access Access // who may subscribe via topic_{slug}.subscribe(); default AccessUser
// PerUser forbids broadcast: Publish panics, only PublishToUser delivers
// (to the named user's subscribed conversations). Use for personal feeds
// (reminders, alerts) where a broadcast would leak across users.
PerUser bool
}
Topic is the self-contained declaration registered via agent.RegisterTopic. Conversations subscribe to a topic via topic_{slug}.subscribe() in run_js; builders publish via the *TopicHandle returned by RegisterTopic.
type TopicDef ¶
type TopicDef struct {
Slug string `json:"slug"`
Description string `json:"description"`
LLMHint string `json:"llmHint,omitempty"`
Access Access `json:"access"`
PerUser bool `json:"perUser,omitempty"`
}
TopicDef is the wire format sent in SyncRequest.
type TopicHandle ¶
type TopicHandle struct {
// contains filtered or unexported fields
}
TopicHandle is a compile-time binding to a registered topic. Returned by Agent.RegisterTopic; used for type-safe publishing.
func (*TopicHandle) Publish ¶
func (h *TopicHandle) Publish(ctx context.Context, parts []DisplayPart) error
Publish sends display parts to all conversations subscribed to this topic. It panics on a PerUser topic — those deliver only via PublishToUser, so a broadcast would leak one user's content to every subscriber.
func (*TopicHandle) PublishToUser ¶
func (h *TopicHandle) PublishToUser(ctx context.Context, userID string, parts []DisplayPart) error
PublishToUser sends display parts only to the given user's conversations subscribed to this topic. userID is the internal-user uuid (User.ID).
type UnsealRequest ¶ added in v0.3.0
type UnsealRequest struct {
Sealed string `json:"sealed"`
}
UnsealRequest / UnsealResponse are the wire bodies of POST /api/agent/unseal: the agent posts a previously-sealed blob, airlock returns the plaintext (only if the blob was sealed for this same agent).
type UnsealResponse ¶ added in v0.3.0
type UnsealResponse struct {
Plaintext string `json:"plaintext"`
}
type User ¶
User identifies the human a run is acting for, exposed to handler code via UserFromContext and to run_js as the `user` global. ID is the stable internal-user uuid (the key to scope agent-owned data by); Email/DisplayName are display claims. All fields are empty for cron/schedule/webhook and anonymous runs.
func UserFromContext ¶
UserFromContext returns the human a run is acting for. The second return is false for runs with no originating user — cron/schedule/webhook triggers and anonymous/public prompt runs. ID is the stable internal-user uuid and is the key to scope agent-owned data by; Email/DisplayName are display claims. Reading it never materializes a run, so route handlers can call it freely: /prompt and route runs carry id+email+display name (airlock forwards X-User-ID/Email/Name); the A2A path carries id only.
type Webhook ¶
type Webhook struct {
Path string // unique per agent
Handler WebhookHandlerFunc // required
Verify string // "none" | "hmac" | "token" | "bearer" | "ed25519" (default: "none")
Header string // header carrying the signature/token (hmac/ed25519 modes)
Timeout time.Duration // max execution time (default: 2 min)
Description string
Access Access // who may invoke; default AccessUser
}
Webhook is the self-contained declaration registered via agent.RegisterWebhook. Agents serve incoming HTTP at /webhook/{Path} on their container.
type WebhookDef ¶
type WebhookDef struct {
Path string `json:"path"`
Verify string `json:"verify"`
Header string `json:"header,omitempty"`
TimeoutMs int64 `json:"timeoutMs"`
Description string `json:"description,omitempty"`
}
WebhookDef is a webhook definition sent during sync.
type WebhookHandlerFunc ¶
type WebhookHandlerFunc func(ctx context.Context, data []byte, ew *EventWriter) error
WebhookHandlerFunc handles incoming webhook requests. Pass ctx to any agent.X(ctx, ...) call the body makes.
Source Files
¶
- access.go
- agent.go
- agentsdk.go
- assets.go
- background.go
- busbridge.go
- client.go
- codec.go
- connection.go
- context.go
- db.go
- directtools.go
- directtools_builtins.go
- directtools_dynamic.go
- editops.go
- env.go
- envelope.go
- eventwriter.go
- exec.go
- filecache.go
- fileops.go
- files.go
- gen.go
- instruction.go
- lazy_run.go
- logging.go
- mcp.go
- migrate.go
- model_slot.go
- prompt.go
- provider.go
- recording.go
- register.go
- run.go
- schedule.go
- seal.go
- search.go
- sensitive.go
- serve.go
- session_store.go
- sibling.go
- solagent.go
- spillwriter.go
- storage.go
- storage_sync.go
- sync.go
- testing.go
- tool.go
- tools.go
- topic.go
- types.go
- vm.go
- vm_limits.go
- vm_media.go
Directories
¶
| Path | Synopsis |
|---|---|
|
Package agenttest provides helpers for testing agents built on agentsdk.
|
Package agenttest provides helpers for testing agents built on agentsdk. |
|
cmd
|
|
|
air
command
Command air authors and maintains Airlock agent repos outside airlock.
|
Command air authors and maintains Airlock agent repos outside airlock. |
|
internal
|
|
|
Package prompt renders the agent's system prompt at run time from the agent's live registrations (tools, connections, MCPs, topics, webhooks, crons, routes) plus the small slice of platform data Airlock supplies at sync (dashboard/route URLs, the sibling address book).
|
Package prompt renders the agent's system prompt at run time from the agent's live registrations (tools, connections, MCPs, topics, webhooks, crons, routes) plus the small slice of platform data Airlock supplies at sync (dashboard/route URLs, the sibling address book). |
|
Package scaffold materializes agent project templates.
|
Package scaffold materializes agent project templates. |
|
Package tsrender produces the TypeScript .d.ts blocks that Airlock embeds into the agent system prompt: typed signatures for registered tools, and per-server `declare const mcp_{slug}: {...}` namespaces for MCP tools.
|
Package tsrender produces the TypeScript .d.ts blocks that Airlock embeds into the agent system prompt: typed signatures for registered tools, and per-server `declare const mcp_{slug}: {...}` namespaces for MCP tools. |