agentsdk

package module
v0.5.0 Latest Latest
Warning

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

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

README

agentsdk

Go SDK for building cyborg agents — programs that are half code, half AI — that run on Airlock.

Cyborg agents are deterministic Go where it makes sense (HTTP routes, webhooks, cron jobs, structured tool execution) and AI-driven where it helps (LLM reasoning, conversation handling, open-ended decisions). agentsdk is the contract your code uses to participate in the airlock platform: register routes, tools, webhooks, crons, and chat surfaces; access scoped storage and per-agent Postgres; and call LLMs through the platform's credential-managing proxy.

If you're not building on Airlock, you don't need this — agentsdk is the glue, not the runtime.

Read the Airlock documentation for platform guides and the Agent SDK and CLI guide for the authoring workflow.

Install

go get github.com/airlockrun/agentsdk

Requires Go 1.26+.

Air CLI

Install the global launcher once:

go install github.com/airlockrun/agentsdk/cmd/airlock@latest

The launcher selects the Agent SDK version advertised by the target Airlock and creates a repository that pins its own CLI:

airlock init my-app --url https://airlock.example.com
airlock clone existing-app --url https://airlock.example.com my-app

Inside an agent repository, use the pinned tool for authoring and deployment:

go tool air toolchain install
go tool air build
go tool air deploy -m "Describe this deployment"

Running airlock inside an agent repository delegates non-bootstrap commands to go tool air; the repository's go.mod remains the version source of truth.

For package-level SDK details and runtime contracts, read the agentsdk reference. Its focused companions cover object storage, interactive authentication, and Postgres-backed agents.

Hello-world agent

package main

import (
	"fmt"
	"net/http"

	"github.com/airlockrun/agentsdk"
)

func main() {
	agent := agentsdk.New(agentsdk.Config{
		Description: "Greets visitors. Replace once the agent does real work.",
	})

	agent.RegisterRoute(&agentsdk.Route{
		Method: http.MethodGet,
		Path:   "/",
		Handler: func(w http.ResponseWriter, r *http.Request) error {
			_, err := fmt.Fprintln(w, "hello from a cyborg agent")
			return err
		},
		Access:      agentsdk.AccessPublic,
		Description: "Greet anyone who visits the home route.",
	})

	agent.Serve()
}

In a real agent you'd also call RegisterTool, RegisterWebhook, RegisterJob, RegisterConnection, and so on. The API reference documents the full surface.

Lifecycle

agentsdk.New creates a definition-only agent. Construct services, wire the late-bound handles returned by APIs such as Agent.DB(), and register every declaration in your factory. This phase does not read runtime environment variables, open the database, make network calls, or run migrations. Database operations through the Agent.DB() handle are unavailable until startup.

Agent.Manifest() freezes declarations and returns the complete canonical manifest without starting runtime dependencies. Running the agent with AIRLOCK_AGENT_MODE=manifest emits that manifest as one JSON line and exits, so Airlock can inspect an agent offline.

In normal mode, Agent.Serve() freezes declarations, starts the runtime and runs migrations, synchronizes the manifest with Airlock, runs named process-local OnStart hooks, then serves HTTP until shutdown. Keep durable startup work in registered jobs; OnStart is for disposable process-local state.

Tests use agenttest.New(t, factory). It invokes the factory first while the agent is definition-only, then provisions the mock Airlock and test database, starts the runtime, validates migrations with an up, down-to-zero, up cycle, synchronizes declarations, runs OnStart hooks, and returns a ready agent. go tool air build provisions one throwaway PostgreSQL container for the serial test run instead of starting one per agenttest.New call.

Companion projects

  • airlock (AGPL-3.0) — the self-hosted platform that runs agents built with this SDK
  • goai (Apache-2.0) — Go port of the Vercel AI SDK
  • sol (Apache-2.0) — agent runtime / CLI utility

License

Apache-2.0.

Contributing

See CONTRIBUTING.md and CODE_OF_CONDUCT.md. A CLA Assistant bot will prompt you to sign on your first PR (one signature covers all airlockrun projects).

Security

Email security@airlock.run. Do not open public issues for vulnerabilities.

Documentation

Overview

Package agentsdk provides the Go SDK for building Airlock agents.

Agents register typed tools, routes, webhooks, jobs, connections, and other capabilities, then serve them through the Airlock runtime. See the repository README and REFERENCE.md for the project guide and complete API reference.

Index

Constants

View Source
const HTMXVersion = "2.0.10"

HTMXVersion is the version of htmx the asset route serves.

View Source
const MaxBufferedResponseBytes = 20 << 20 // 20 MiB

MaxBufferedResponseBytes is the cap Request enforces for buffered responses.

View Source
const Version = "0.5.0"

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

View Source
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. Use the lucide subpackage for the bundled icon catalog. Register other embedded images, page-specific CSS, and fonts with RegisterStaticAsset.

View Source
var ErrAgentURLUnavailable = errors.New("agentsdk: agent URL is unavailable")

ErrAgentURLUnavailable means the agent's public URL is not available from the current value or context. Serve syncs with Airlock before accepting requests, so deployed handlers normally have a URL; tests that call Handler directly may need to sync first or omit absolute links.

View Source
var ErrInvalidPath = errors.New("agentsdk: invalid path")

ErrInvalidPath is returned for paths that fail normalization (missing leading '/', empty segments, '..' segments, etc.).

View Source
var ErrJobEnqueueUnavailable = errors.New("agentsdk: job enqueue unavailable")

ErrJobEnqueueUnavailable identifies a temporary deployment-state rejection of an exact job handler contract.

View Source
var ErrNotFound = errors.New("agentsdk: file not found")

ErrNotFound is returned by ResolveFilePath 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.

View Source
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 AgentURLFromContext added in v0.4.0

func AgentURLFromContext(ctx context.Context) (string, error)

AgentURLFromContext returns AgentURL for the agent bound to ctx.

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"})

Types

type Access

type Access string

Access defines who can reach a tool, connection, MCP, topic, or storage zone.

const (
	AccessAdmin  Access = "admin"
	AccessUser   Access = "user"
	AccessPublic Access = "public"
)

type Agent

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

Agent is a long-lived singleton, one per container. New creates it in the definition phase; Serve starts its runtime after registrations are complete.

func AgentFromContext

func AgentFromContext(ctx context.Context) *Agent

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 New

func New(cfg Config) *Agent

New creates an Agent for dependency wiring and registrations. It performs no database, network, credential, migration, or other runtime initialization; Serve starts the runtime after declarations are complete. Panics if Config.Description is empty.

func (*Agent) AddInstruction added in v0.4.0

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 job handlers run arbitrary Go code and build their own prompts.

func (*Agent) AddSensitive

func (a *Agent) AddSensitive(values ...string)

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) AgentURL added in v0.4.0

func (a *Agent) AgentURL() (string, error)

AgentURL returns the agent's public origin, e.g. https://todo.example.com. Use relative paths such as "/auth" for links within the same agent UI; use AgentURL only when an absolute URL leaves the current request context, such as emails, third-party callbacks, or messages.

func (*Agent) Close added in v0.4.0

func (a *Agent) Close() error

Close releases the database pool owned by a started Agent. Serve calls Close when it stops; tests that call Start directly should also close the Agent.

func (*Agent) CopyFile added in v0.2.0

func (a *Agent) CopyFile(ctx context.Context, src, dst string) error

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

func (a *Agent) DB() *AgentDB

DB returns the Agent's owned database pool. 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

func (a *Agent) DeleteFile(ctx context.Context, path string) error

DeleteFile removes a file. Idempotent — missing files do not error. Trusted: no access check.

func (*Agent) Embed added in v0.4.0

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

func (a *Agent) EmbeddingModel(ctx context.Context, slug string) model.EmbeddingModel

EmbeddingModel returns an embedding model for the registered slot `slug`. Panics unless `slug` is registered with CapEmbedding.

func (*Agent) GenerateImage added in v0.4.0

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 added in v0.4.0

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 added in v0.4.0

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 added in v0.4.0

func (a *Agent) Handler() http.Handler

Handler builds the agent's HTTP mux: the framework routes (/prompt, /webhook, /job, /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 requires a started runtime, validates and freezes registrations, and does not listen. Tests use it after agenttest.New to exercise routes through the real dispatch (including {param} extraction) with httptest.

func (*Agent) ImageModel

func (a *Agent) ImageModel(ctx context.Context, slug string) model.ImageModel

ImageModel returns an image generation model for the registered slot `slug`. Panics unless `slug` is registered with CapImage.

func (*Agent) LLM

func (a *Agent) LLM(ctx context.Context, slug string) stream.Model

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

func (a *Agent) ListDir(ctx context.Context, path string, opts ListOpts) ([]FileInfo, error)

ListDir enumerates files under `path`. Trusted: no access check. The empty string lists the agent root.

func (*Agent) Logger added in v0.3.0

func (a *Agent) Logger(ctx context.Context) *zap.Logger

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) Manifest added in v0.5.0

func (a *Agent) Manifest() wire.AgentManifest

Manifest freezes registrations and returns the complete canonical agent declaration used by both offline inspection and runtime synchronization.

func (*Agent) OnStart added in v0.5.0

func (a *Agent) OnStart(name string, run func(context.Context) error)

OnStart registers process-local initialization that runs once after the runtime is connected and synchronized, before the agent becomes ready. Startup hooks must only construct disposable local state; durable work belongs in a registered job.

func (*Agent) OpenFile added in v0.2.0

func (a *Agent) OpenFile(ctx context.Context, path string) (io.ReadCloser, error)

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

func (a *Agent) ReadFile(ctx context.Context, path string) ([]byte, error)

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

func (a *Agent) ReadRange(ctx context.Context, path string, start, end int64) ([]byte, error)

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) 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 ResolveFilePath.

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 ResolveFilePath, 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.ResolveFilePath 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) RegisterMCP

func (a *Agent) RegisterMCP(m *MCP) *MCPHandle

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

func (a *Agent) RegisterModel(slot *ModelSlot)

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

func (a *Agent) RegisterRoute(r *Route)

RegisterRoute installs a custom HTTP route served by this agent and proxied via Airlock's subdomain routing.

func (*Agent) RegisterStaticAsset added in v0.4.0

func (a *Agent) RegisterStaticAsset(asset *StaticAsset)

RegisterStaticAsset registers a public static asset. Registrations are copied, validated, and frozen with the Agent's other declarations.

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. 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

func (a *Agent) RegisterWebhook(w *Webhook)

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) ResolveFilePath added in v0.4.0

func (a *Agent) ResolveFilePath(ctx context.Context, path string, op FileOperation) (FilePath, error)

ResolveFilePath authorizes an untrusted path and returns the exact physical path that storage operations must use. Trusted Go storage methods bypass it.

func (*Agent) Seal added in v0.3.0

func (a *Agent) Seal(ctx context.Context, plaintext string) (string, error)

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 emits one canonical declaration manifest to stdout and returns when AIRLOCK_AGENT_MODE=manifest. Otherwise it starts the agent runtime and HTTP server, blocking until SIGINT/SIGTERM.

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 resolves LLM-supplied paths via ResolveFilePath.

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

func (a *Agent) SpeechModel(ctx context.Context, slug string) model.SpeechModel

SpeechModel returns a text-to-speech model for the registered slot `slug`. Panics unless `slug` is registered with CapSpeech.

func (*Agent) Start added in v0.5.0

func (a *Agent) Start(ctx context.Context) (retErr error)

Start freezes declarations, initializes runtime dependencies, synchronizes the manifest with Airlock, and runs process-local startup hooks. It does not start the HTTP server. Most applications call Serve, which calls Start.

func (*Agent) StatFile added in v0.2.0

func (a *Agent) StatFile(ctx context.Context, path string) (FileInfo, error)

StatFile returns metadata for a file. Trusted: no access check.

func (*Agent) StreamText added in v0.4.0

func (a *Agent) StreamText(ctx context.Context, input stream.Input) (*stream.Result, error)

StreamText is the streaming counterpart of GenerateText.

func (*Agent) SyncDown added in v0.2.1

func (a *Agent) SyncDown(ctx context.Context, prefix, localDir string) error

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 recurring job 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

func (a *Agent) SyncUp(ctx context.Context, localDir, prefix string) error

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 added in v0.4.0

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

func (a *Agent) TranscriptionModel(ctx context.Context, slug string) model.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

func (a *Agent) Unseal(ctx context.Context, sealed string) (string, error)

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 added in v0.4.0

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.

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.

func (*AgentDB) ExecContext

func (a *AgentDB) ExecContext(ctx context.Context, query string, args ...any) (sql.Result, error)

ExecContext satisfies sqlc's DBTX. Forwards to the underlying *sql.DB.

func (*AgentDB) PingContext

func (a *AgentDB) PingContext(ctx context.Context) error

PingContext checks database connectivity.

func (*AgentDB) PrepareContext

func (a *AgentDB) PrepareContext(ctx context.Context, query string) (*sql.Stmt, error)

PrepareContext satisfies sqlc's DBTX. Forwards to the underlying *sql.DB.

func (*AgentDB) QueryContext

func (a *AgentDB) QueryContext(ctx context.Context, query string, args ...any) (*sql.Rows, error)

QueryContext satisfies sqlc's DBTX. Forwards to the underlying *sql.DB.

func (*AgentDB) QueryRowContext

func (a *AgentDB) QueryRowContext(ctx context.Context, query string, args ...any) *sql.Row

QueryRowContext satisfies sqlc's DBTX. Forwards to the underlying *sql.DB.

func (*AgentDB) Transaction added in v0.4.0

func (a *AgentDB) Transaction(ctx context.Context, opts *sql.TxOptions, fn func(DBTX) error) error

Transaction runs fn in a transaction. The callback receives the DBTX shape accepted by sqlc-generated New functions. A nil callback panics; returning an error rolls back, and returning nil commits.

type AuthInjection

type AuthInjection struct {
	Type AuthInjectionType `json:"type"`
	Name string            `json:"name,omitempty"`
	// contains filtered or unexported fields
}

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 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
	// contains filtered or unexported fields
}

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         // required
	Description string         // required: shown to users and the LLM
	BaseURL     string         // required: absolute HTTP(S) URL
	AuthMode    ConnectionAuth // required
	AuthURL     string
	TokenURL    string
	Scopes      []string
	// AuthParams are extra query parameters added to the OAuth
	// authorization request. OAuth identity, callback, state, response type,
	// and PKCE parameters are reserved and rejected during registration.
	// 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 RequestOpts.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 // required: who may invoke conn_{slug}
	// contains filtered or unexported fields
}

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 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 DBTX added in v0.4.0

type DBTX interface {
	ExecContext(context.Context, string, ...any) (sql.Result, error)
	PrepareContext(context.Context, string) (*sql.Stmt, error)
	QueryContext(context.Context, string, ...any) (*sql.Rows, error)
	QueryRowContext(context.Context, string, ...any) *sql.Row
}

DBTX is the database surface accepted by sqlc-generated constructors. Both the agent pool and transaction callback values implement it.

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 DirectoryOpts added in v0.2.0

type DirectoryOpts struct {
	Read        Access // required
	Write       Access // required
	List        Access // required
	Description string // required: shown to the LLM

	// LLMHint is optional model-facing guidance.
	LLMHint string

	// RetentionHours opts files into age-based cleanup. Zero disables cleanup.
	RetentionHours int

	// Scope controls identity partitioning. ScopeNone disables partitioning.
	Scope DirectoryScope
	// contains filtered or unexported fields
}

DirectoryOpts is the option struct accepted by RegisterDirectory.

type DirectoryScope added in v0.3.0

type DirectoryScope string

DirectoryScope opts a directory into identity-partitioned path scoping. Empty string ("" / ScopeNone) leaves the directory unpartitioned.

Each value selects exactly one identity: the originating user, current conversation, or current run. ResolveFilePath fails when that identity is absent and never falls back to another scope kind.

const (
	ScopeNone         DirectoryScope = ""
	ScopeRun          DirectoryScope = "run"
	ScopeConversation DirectoryScope = "conv"
	ScopeUser         DirectoryScope = "user"
)

type DisplayPart

type DisplayPart struct {
	Type     DisplayPartType `json:"type"`
	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
	// contains filtered or unexported fields
}

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 DisplayPartType added in v0.4.0

type DisplayPartType string

DisplayPartType identifies a user-facing content block.

const (
	DisplayPartTypeText  DisplayPartType = "text"
	DisplayPartTypeImage DisplayPartType = "image"
	DisplayPartTypeFile  DisplayPartType = "file"
	DisplayPartTypeAudio DisplayPartType = "audio"
	DisplayPartTypeVideo DisplayPartType = "video"
)

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
	// contains filtered or unexported fields
}

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 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 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 handlers.

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 FileOperation added in v0.4.0

type FileOperation string

FileOperation identifies an untrusted storage operation for ResolveFilePath.

const (
	FileOperationRead      FileOperation = "read"
	FileOperationList      FileOperation = "list"
	FileOperationWrite     FileOperation = "write"
	FileOperationOverwrite FileOperation = "overwrite"
	FileOperationDelete    FileOperation = "delete"
)

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 HTTPError added in v0.4.0

type HTTPError struct {
	Status  int
	Message string
	Cause   error
}

HTTPError carries a safe response and an internal cause from a route handler. The SDK writes Message to the caller and records/logs Cause.

func NewHTTPError added in v0.4.0

func NewHTTPError(status int, message string, cause error) *HTTPError

NewHTTPError constructs an HTTPError for a 4xx or 5xx response.

func (*HTTPError) Error added in v0.4.0

func (e *HTTPError) Error() string

func (*HTTPError) Unwrap added in v0.4.0

func (e *HTTPError) Unwrap() error

type Instruction added in v0.4.0

type Instruction struct {
	Text   string
	Access []Access
	// contains filtered or unexported fields
}

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 Job added in v0.5.0

type Job[In, Out any] struct {
	Name           string                  // lowercase snake_case, unique with Version
	Version        int                     // positive immutable contract version
	Description    string                  // required: shown to operators
	Timeout        time.Duration           // required maximum execution time
	MaxAttempts    int                     // required, including the first attempt
	MaxConcurrency int                     // required per-agent handler concurrency
	Handler        JobHandlerFunc[In, Out] // required
	// contains filtered or unexported fields
}

Job declares one versioned background-job contract.

type JobContext added in v0.5.0

type JobContext struct {
	ID          string
	Attempt     int
	ScheduledAt *time.Time
}

JobContext identifies one delivery attempt for a durable background job. Handlers use ID as the idempotency key because delivery is at least once.

func (JobContext) ReportProgress added in v0.5.0

func (job JobContext) ReportProgress(ctx context.Context, progress JobProgress) error

ReportProgress synchronously records progress for this delivery attempt.

type JobCron added in v0.5.0

type JobCron[In any] struct {
	Slug        string // lowercase snake_case, unique across the agent
	Schedule    string // standard cron expression, e.g. "0 9 * * *"
	Input       In     // static input included with every enqueue
	Description string // required: shown to operators
	// contains filtered or unexported fields
}

JobCron declares a recurring enqueue of one registered job contract.

type JobEnqueueUnavailableError added in v0.5.0

type JobEnqueueUnavailableError struct {
	HandlerName    string
	HandlerVersion int
	Message        string
}

JobEnqueueUnavailableError reports the exact handler contract Airlock could not accept during a deployment transition.

func (*JobEnqueueUnavailableError) Error added in v0.5.0

func (*JobEnqueueUnavailableError) Unwrap added in v0.5.0

func (e *JobEnqueueUnavailableError) Unwrap() error

type JobHandle added in v0.5.0

type JobHandle[In, Out any] struct {
	// contains filtered or unexported fields
}

JobHandle binds a typed job contract to an agent. Enqueue operations are added to this handle with the durable job lifecycle API.

func RegisterJob added in v0.5.0

func RegisterJob[In, Out any](a *Agent, job *Job[In, Out]) *JobHandle[In, Out]

RegisterJob registers a typed, versioned background-job handler. The same name may have multiple versions so queued work remains executable across compatible agent deployments.

func (*JobHandle[In, Out]) Cancel added in v0.5.0

func (h *JobHandle[In, Out]) Cancel(ctx context.Context, id string) error

Cancel requests cancellation of a queued or running job.

func (*JobHandle[In, Out]) Cron added in v0.5.0

func (h *JobHandle[In, Out]) Cron(cron *JobCron[In])

Cron registers a recurring enqueue targeting this exact job contract.

func (*JobHandle[In, Out]) Enqueue added in v0.5.0

func (h *JobHandle[In, Out]) Enqueue(ctx context.Context, id string, input In) (JobResult[Out], error)

Enqueue durably accepts one caller-identified job. Retrying the same work and ID from the same source run while Airlock retains the job is idempotent and returns the existing job.

func (*JobHandle[In, Out]) EnqueueAt added in v0.5.0

func (h *JobHandle[In, Out]) EnqueueAt(ctx context.Context, id string, fireAt time.Time, input In) (JobResult[Out], error)

EnqueueAt durably accepts one caller-identified job for delivery at fireAt. Retrying the same work and ID from the same source run while Airlock retains the job is idempotent and returns the existing job.

func (*JobHandle[In, Out]) Get added in v0.5.0

func (h *JobHandle[In, Out]) Get(ctx context.Context, id string) (JobResult[Out], error)

Get returns the current durable state of a job.

func (*JobHandle[In, Out]) Name added in v0.5.0

func (h *JobHandle[In, Out]) Name() string

Name returns the registered handler name.

func (*JobHandle[In, Out]) Version added in v0.5.0

func (h *JobHandle[In, Out]) Version() int

Version returns the registered handler contract version.

type JobHandlerFunc added in v0.5.0

type JobHandlerFunc[In, Out any] func(ctx context.Context, job JobContext, input In) (Out, error)

JobHandlerFunc handles one typed background-job delivery attempt.

type JobProgress added in v0.5.0

type JobProgress struct {
	Phase     string
	Message   string
	Completed int64
	Total     int64
}

JobProgress is the latest durable progress reported by a job attempt.

type JobResult added in v0.5.0

type JobResult[Out any] struct {
	ID           string
	Status       JobStatus
	AttemptCount int
	MaxAttempts  int
	AttemptLimit int
	LastError    string
	Progress     *JobProgress
	SourceRunID  string
	ScheduledAt  *time.Time
	CreatedAt    time.Time
	UpdatedAt    time.Time
	StartedAt    *time.Time
	CompletedAt  *time.Time
	Output       *Out
	Created      bool
}

JobResult reports durable lifecycle state and the typed output of a successful job. Created is true only when Enqueue accepted a new job rather than returning the existing job for the same ID.

type JobStatus added in v0.5.0

type JobStatus string

JobStatus is the durable lifecycle state of a background job.

const (
	JobStatusQueued    JobStatus = "queued"
	JobStatusRunning   JobStatus = "running"
	JobStatusSucceeded JobStatus = "succeeded"
	JobStatusFailed    JobStatus = "failed"
	JobStatusCancelled JobStatus = "cancelled"
)

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
	// contains filtered or unexported fields
}

ListOpts controls ListDir.

type MCP

type MCP struct {
	Slug     string  // unique per agent; binds as mcp_{slug} in run_js
	Name     string  // required
	URL      string  // required: absolute HTTP(S) URL
	AuthMode MCPAuth // required
	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 // required: who may invoke mcp_{slug}
	// contains filtered or unexported fields
}

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.

const (
	MCPAuthOAuth          MCPAuth = "oauth"
	MCPAuthOAuthDiscovery MCPAuth = "oauth_discovery"
	MCPAuthToken          MCPAuth = "token"
	MCPAuthNone           MCPAuth = "none"
)

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 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 MCPToolCallResponse

type MCPToolCallResponse struct {
	Content []MCPContent `json:"content"`
	IsError bool         `json:"isError"`
}

MCPToolCallResponse is returned from MCP tool call proxy.

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 ModelSlot

type ModelSlot struct {
	Slug        string
	Capability  ModelCapability // required: CapText, CapVision, CapImage, CapSpeech, CapTranscription, CapEmbedding, or CapSearch
	Description string          // required: human-readable hint shown in the admin UI
	// contains filtered or unexported fields
}

ModelSlot is the self-contained declaration registered via agent.RegisterModel.

type RegisterOption added in v0.4.0

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 added in v0.4.0

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
	// contains filtered or unexported fields
}

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           // required: shown to users and the LLM
	// contains filtered or unexported fields
}

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 RouteHandlerFunc

type RouteHandlerFunc func(w http.ResponseWriter, r *http.Request) error

RouteHandlerFunc handles custom HTTP routes registered via RegisterRoute. Handler code uses r.Context() for agent calls and returns execution errors to the SDK. Errors are recorded on a materialized run and served as a generic 500 response when the handler has not already written a response.

type ShareFileResponse added in v0.2.1

type ShareFileResponse struct {
	URL         string `json:"url"`
	ExpiresAtMs int64  `json:"expiresAtMs"`
}

ShareFileResponse is returned by POST /api/agent/storage/share. URL is unauthenticated and valid until ExpiresAtMs (ms epoch).

type StaticAsset added in v0.4.0

type StaticAsset struct {
	Name        string
	ContentType string
	Data        []byte
	// contains filtered or unexported fields
}

StaticAsset declares immutable, in-memory content served publicly from /static/{Name}. Name should include a content hash when the bytes can change because browsers cache successful responses for one year.

type Topic

type Topic struct {
	Slug        string
	Description string
	LLMHint     string // optional model-only guidance
	Access      Access // required: who may subscribe via topic_{slug}.subscribe()
	// 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
	// contains filtered or unexported fields
}

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 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 added in v0.4.0

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 User added in v0.4.0

type User struct {
	ID          string
	Email       string
	DisplayName string
}

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 system job/webhook and anonymous runs.

func UserFromContext added in v0.4.0

func UserFromContext(ctx context.Context) (User, bool)

UserFromContext returns the human a run is acting for. The second return is false for runs with no originating user, including system job and 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      WebhookVerification // required
	Header      string              // signature header (required for hmac, optional for ed25519)
	Timeout     time.Duration       // max execution time (default: 2 min)
	Description string              // required: shown to users and the LLM
	// contains filtered or unexported fields
}

Webhook is the self-contained declaration registered via agent.RegisterWebhook. Agents serve incoming HTTP at /webhook/{Path} on their container.

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.

type WebhookVerification added in v0.4.0

type WebhookVerification string

WebhookVerification selects how an incoming webhook is authenticated.

const (
	WebhookVerificationNone    WebhookVerification = "none"
	WebhookVerificationHMAC    WebhookVerification = "hmac"
	WebhookVerificationToken   WebhookVerification = "token"
	WebhookVerificationBearer  WebhookVerification = "bearer"
	WebhookVerificationEd25519 WebhookVerification = "ed25519"
)

Directories

Path Synopsis
Package agenttest provides agent environments, platform mocks, and caller contexts for tests of agents built on agentsdk.
Package agenttest provides agent environments, platform mocks, and caller contexts for tests of 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.
airlock command
Command airlock bootstraps and dispatches the repository-pinned Air CLI.
Command airlock bootstraps and dispatches the repository-pinned Air CLI.
internal
binding
Package binding maps canonical capabilities to stable presentation names.
Package binding maps canonical capabilities to stable presentation names.
bootstrap
Package bootstrap validates the temporary module used to select the Air CLI before an agent repository exists.
Package bootstrap validates the temporary module used to select the Air CLI before an agent repository exists.
cmd/syncskills command
prompt
Package prompt renders the agent's system prompt from live registrations and caller-specific platform data.
Package prompt renders the agent's system prompt from live registrations and caller-specific platform data.
tsrender
Package tsrender produces TypeScript declarations for agent capabilities.
Package tsrender produces TypeScript declarations for agent capabilities.
Package lucide renders the complete Lucide icon catalog as inline templ components.
Package lucide renders the complete Lucide icon catalog as inline templ components.
Package scaffold materializes agent project templates.
Package scaffold materializes agent project templates.
Package sourcebundle defines the source tree Airlock synchronizes with local workspaces.
Package sourcebundle defines the source tree Airlock synchronizes with local workspaces.
Package wire defines the internal JSON protocol exchanged by agent runtimes and Airlock.
Package wire defines the internal JSON protocol exchanged by agent runtimes and Airlock.

Jump to

Keyboard shortcuts

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