builder

package
v0.4.0 Latest Latest
Warning

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

Go to latest
Published: Jul 25, 2026 License: AGPL-3.0 Imports: 56 Imported by: 0

Documentation

Overview

Package builder implements the agent build and upgrade pipeline.

Index

Constants

View Source
const GitPollInterval = 5 * time.Minute

GitPollInterval is the cadence at which connected agents are checked for new commits on their default branch. Mirrors the OAuth refresh cadence; users behind firewalls or on providers without webhook support (Bitbucket/Gitea in v1) rely on this loop entirely.

Variables

View Source
var ErrDeploymentConflict = errors.New("agent deployment cancelled by a concurrent lifecycle change")

ErrDeploymentConflict means another lifecycle operation changed the agent after this build observed its state. The caller must leave the live lifecycle status untouched.

View Source
var ErrUpgradeInProgress = errors.New("upgrade already in progress")

ErrUpgradeInProgress is returned when an upgrade is already running for the agent.

Functions

func AgentRepoPath added in v0.4.0

func AgentRepoPath(basePath, agentID string) string

Per-agent repo layout:

<basePath>/                     ← AgentReposPath
├── <agentID-1>/                ← per-agent repo working tree
│   ├── .git/
│   ├── main.go
│   ├── go.mod
│   ├── ...
├── <agentID-2>/
│   └── ...

AgentRepoPath returns the working-tree path for a single agent's repo.

func CleanWorktree added in v0.4.0

func CleanWorktree(repoPath string) error

CleanWorktree removes untracked files and directories from the working tree. It respects .gitignore (no -x), so airlock-generated ignored files (DIAGNOSTICS.md, Dockerfile) and regenerated build artefacts (*_templ.go, views/static) are left alone — only stray tracked-then-orphaned sources are removed. Used on a fresh build so files left behind by a prior failed build/codegen don't leak into the docker build context, which is the repo working tree itself.

func CommitScaffold

func CommitScaffold(repoPath string, data scaffold.ScaffoldData) (string, error)

CommitScaffold creates branch build/init in the agent's repo, materializes scaffold files at the repo root, and commits. Returns the commit hash. Safe to call on retry — deletes a stale branch first.

func CommitWorktree added in v0.4.0

func CommitWorktree(repoPath, message string) (hash string, committed bool, err error)

CommitWorktree stages and commits all changes in repoPath's working tree. Returns (HEAD, false, nil) without committing when the tree is clean, so callers can fall through to "current HEAD is the source ref". No push.

func CopyAgentRepo added in v0.4.0

func CopyAgentRepo(basePath, srcID, dstID string) error

CopyAgentRepo clones the srcID agent's repo into the dstID path as an independent repo — committed code + history, working tree checked out, no upstream remote (the clone is a new agent, not a fork tracking the source's on-disk path). --no-hardlinks so the two repos never share object storage. Used by agent clone; the destination must not already exist.

func CreateBranch

func CreateBranch(repoPath, branch string) error

CreateBranch creates a new branch from main in repoPath.

func CreateUpgradeBranch

func CreateUpgradeBranch(repoPath, runID string) error

CreateUpgradeBranch creates branch upgrade/{runID} in the agent's repo off main. Uses `-B` (create-or-reset) so retries don't fail on a leftover branch from a prior failed attempt — Fix-this-error reuses the failed run's id as the branch suffix, and successful upgrades currently never delete the branch after merging, so collisions are easy to hit. Whatever was on the old branch was from a failed run and not worth keeping.

func EnsureGitIdentity added in v0.4.0

func EnsureGitIdentity(repoPath string) error

EnsureGitIdentity sets the local git committer identity Airlock uses for repos it owns. It is safe to call repeatedly and never depends on global git config, which is not present in production containers.

func InitAgentRepo added in v0.4.0

func InitAgentRepo(basePath, agentID string) error

InitAgentRepo initializes a per-agent git repo at <basePath>/<agentID>/ if it doesn't already exist. Idempotent. The initial commit (with a bootstrap .gitignore) gives the repo a HEAD so subsequent `git checkout main` works without complaints; CommitScaffold overwrites that .gitignore with the canonical scaffold template moments later.

func MaterializeBranch added in v0.4.0

func MaterializeBranch(repoPath, branch, workDir string) error

MaterializeBranch writes the tracked tree at branch into workDir with NO .git — `git archive <branch>` piped into `tar -x`. The codegen toolserver bind-mounts workDir at /workspace, so the LLM (which has bash + git + root in that container) sees plain source files and no git repository to push to or corrupt; airlock owns every git operation host-side in repoPath.

func MergeBranch

func MergeBranch(repoPath, branch string) error

MergeBranch fast-forward merges branch into main, rebasing the branch onto current main first if a parallel commit advanced main while this branch's work was running. With per-agent repos each agent's history is isolated, so the rebase trivially fast-forwards in the common case; the fallback path handles the rare case of a manual commit landing on main (e.g. an operator hot-fix) in parallel with a build.

func MigrationVersionAt added in v0.4.0

func MigrationVersionAt(repoPath, commit string) (int, error)

MigrationVersionAt returns the highest goose version number among files in db/migrations/ at the given commit. Goose's contract is "all migrations get applied to head", so this is the version a deployed container ends up at — the answer rollback needs to give goose as its down-to target. Returns 0 when db/migrations/ is empty or absent.

Parses the leading NNNN_ prefix from each filename (goose's required naming convention). Non-numeric files (README, etc.) are skipped.

func RecoverAgentRepo added in v0.4.0

func RecoverAgentRepo(repoPath string) (recovered bool, err error)

RecoverAgentRepo resets an agent repo to a clean default branch before a build, undoing whatever state a previous (possibly failed) build left behind: an in-progress merge/rebase, a stale `upgrade/*` branch left checked out, and uncommitted working-tree edits (airlock's own scratch — regenerated Dockerfile, a go.mod bump that never got committed, etc.).

Without this, housekeeping (which runs before codegen's own checkout-main) operates on whatever branch was left checked out and commits there, so its go.mod bump never lands on main and the codegen clone takes main's stale committed go.mod.

`git checkout -f main` switches to main AND discards working-tree changes in one shot. The failed `upgrade/*` branches are NOT deleted — they're kept for manual recovery; we just stop being checked out on one. The working-tree edits we discard are never user work: user changes reach the repo as commits (the external remote → PullAgentRepo → main). Codegen mirrors its edits into this working tree and commits them in-place (SyncWorkdirToRepo + CommitWorktree); a build that crashed between the mirror and the commit leaves uncommitted edits here, which is exactly the scratch this discards.

No-op on a fresh path with no .git/ (initial build hasn't run `git init` yet). Returns true iff anything actually needed cleaning up, so the caller can log it — silent recovery hides the underlying crash/abandon pattern.

func RemoveAgentRepo added in v0.4.0

func RemoveAgentRepo(basePath, agentID string) error

RemoveAgentRepo deletes the per-agent repo directory entirely. Idempotent — missing dir is not an error.

func ResetHard added in v0.4.0

func ResetHard(repoPath, commit string) error

ResetHard moves the current branch HEAD to commit and forces the working tree to match. Caller is responsible for saving any forward commits with SaveRef first — they'll be unreachable from any branch after this returns (recoverable via reflog for the default 90-day window). Checks out main first so reset hits the right ref.

func SaveRef added in v0.4.0

func SaveRef(repoPath, refName, commit string) error

SaveRef creates a branch ref pointing at the given commit. Fails loud if the ref already exists — callers pick unique names (typically pre-rollback/{timestamp}) so a collision implies a bug, not a retry-safe noop.

func SyncWorkdirToRepo added in v0.4.0

func SyncWorkdirToRepo(workDir, repoPath string, exclude []string) error

SyncWorkdirToRepo mirrors workDir's file tree onto repoPath's working tree so a subsequent `git add -A` in repoPath records exactly the codegen agent's edits (adds, modifications, AND deletions). Paths in exclude are never copied into repoPath (and removed if already tracked) — used to keep the airlock-injected DIAGNOSTICS.md out of commits. repoPath's .git is never touched; workDir carries no .git (see MaterializeBranch).

func UpgradeBranchName added in v0.4.0

func UpgradeBranchName(runID string) string

UpgradeBranchName returns the branch name an upgrade run lives on inside the agent's repo. Exposed so callers can hand the same name to MaterializeBranch and MergeBranch.

Types

type AutoFixContext added in v0.4.0

type AutoFixContext struct {
	ErrorMessage string
	PanicTrace   string
	InputPayload string
	Actions      string
	Messages     string
	Logs         string // captured log lines from the failed run

	BuildError string // error_message of the agent's most recent failed build
	BuildLog   string // tail of that build's docker log
}

AutoFixContext is the failure trail that gets written into DIAGNOSTICS.md before Sol sees the workspace, so the builder agent can diagnose the failure. Two independent sources: a failed runtime run (ErrorMessage … Logs) and the agent's previous failed build (BuildError/BuildLog — the docker/migration breakage that the prior codegen committed to main but never got deployed). Mirrors the corresponding subset of UpgradeInput.

type BuildInput

type BuildInput struct {
	AgentID          string
	Name             string
	Slug             string
	OwnerPrincipalID string
	InitiatorUserID  pgtype.UUID // user who triggered the build; attributes codegen spend (falls back to owner)
	BuildProviderID  pgtype.UUID // providers row FK; pairs with BuildModel
	BuildModel       string      // bare model name; "" + invalid FK ⇄ inherit system default
	Instructions     string      // optional: when non-empty, run Sol code generation after scaffold
	Message          string      // optional build description persisted without invoking Sol

	// SkipScaffold, when true, provisions the agent's DB schema but does NOT
	// re-run the scaffold (CommitScaffold/MergeBranch/CleanWorktree) that
	// overwrites scaffold-managed files. Set for a clone whose repo is copied
	// in already-complete — re-scaffolding would clobber the source agent's
	// customizations to those files (viewmodel.go, main.go, index.templ, …).
	SkipScaffold bool

	// Optional external-git connection. When GitRemoteURL is non-empty,
	// the agent is connected to the remote during Build and the first
	// push happens via Execute's post-merge push (Phase C2). The API
	// handler enforces that GitCredentialID belongs to OwnerPrincipalID before
	// calling Build.
	GitRemoteURL     string
	GitCredentialID  pgtype.UUID
	GitDefaultBranch string // defaults to "main" when empty
	GitMode          string

	// SystemConversationID, when set, is the system-agent conversation that
	// triggered this build via create_agent. On completion the build outcome
	// is posted back there + the system agent resumes. Empty for the web
	// create path (no conversation).
	SystemConversationID string
}

BuildInput describes what to build.

type BuildKind added in v0.4.0

type BuildKind string

BuildKind discriminates the three flows that share the Execute pipeline.

const (
	BuildKindBuild    BuildKind = "build"
	BuildKindUpgrade  BuildKind = "upgrade"
	BuildKindRollback BuildKind = "rollback"
)

type BuildPlan added in v0.4.0

type BuildPlan struct {
	Agent dbq.Agent
	Kind  BuildKind

	StartCommit    string
	PreserveBranch string

	Instruction string
	Message     string

	// SkipScaffold (BuildKindBuild only) provisions the DB schema but skips the
	// scaffold overwrite — used by clone, whose repo arrives already-complete.
	SkipScaffold bool

	RollbackTargetID pgtype.UUID

	// InitiatorUserID attributes the codegen LLM spend to the user who
	// triggered this build (UI upgrade/rollback, initial create, sysagent
	// mutate). Invalid for unattended builds (git push/poll, SDK mass-rebuild
	// migration) — codegen falls back to the agent owner.
	InitiatorUserID pgtype.UUID

	Scaffold *ScaffoldInputs

	RunID          string
	ConversationID string
	Reason         string

	Diagnostics *AutoFixContext
}

BuildPlan is the single input to Execute. Each external entry point (Build, RunUpgrade, Rollback) constructs one of these and hands it off; Execute is the only function that contains the full pipeline.

Fields are interpreted as follows:

  • Agent / Kind / RunID / Reason / ConversationID: always required.
  • Scaffold: required iff Kind=build (the repo doesn't exist yet).
  • StartCommit + PreserveBranch: rollback only — Phase B saves current HEAD under PreserveBranch then resets main to StartCommit.
  • Instruction: empty = no Sol invocation (bare rebuild / pure rollback); non-empty = Sol runs with this as the user-turn prompt and the string is also persisted on agent_builds.instructions.
  • Message: optional operator-facing build description persisted on agent_builds.instructions without invoking Sol.
  • RollbackTargetID: rollback only — points the new agent_builds row at the build we're rolling back to (UI uses it for the label).
  • Diagnostics: upgrade auto_fix only — writes DIAGNOSTICS.md into the codegen workspace before Sol sees it.

type BuildService

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

BuildService orchestrates the agent build and upgrade pipeline.

func New

func New(cfg *config.Config, database *db.DB, containers container.ContainerManager, encryptor secrets.Store, logger *zap.Logger) *BuildService

New creates a BuildService. Panics if any dependency is nil.

func (*BuildService) AcquireSourceLock added in v0.4.0

func (b *BuildService) AcquireSourceLock(ctx context.Context, agentID string) (*SourceLock, error)

AcquireSourceLock obtains a session advisory lock keyed by the stable agent UUID. Call Unlock exactly once.

func (*BuildService) AcquireUpgradeLock

func (b *BuildService) AcquireUpgradeLock(ctx context.Context, agentID string) error

Upgrade runs the upgrade pipeline for an existing agent. This is synchronous — caller should run in a goroutine if needed. AcquireUpgradeLock atomically checks that no upgrade is running for the agent and sets upgrade_status to "building". Returns ErrUpgradeInProgress if an upgrade is already active. Call RunUpgrade after a successful lock.

func (*BuildService) AgentRepoPath added in v0.4.0

func (b *BuildService) AgentRepoPath(agentID string) string

AgentRepoPath returns the on-disk path for a single agent's repo.

func (*BuildService) Build

func (b *BuildService) Build(_ context.Context, input BuildInput) error

Build runs the initial-build pipeline: scaffold → Sol codegen (if instructions present) → docker build → start container. Thin wrapper over Execute that handles the build-specific outer lifecycle: load or create the agent row, flip agents.status to building, route failures into agents.status=failed. Synchronous; caller runs in a goroutine.

func (*BuildService) CancelBuild

func (b *BuildService) CancelBuild(agentID string) bool

CancelBuild cancels a running build/upgrade for the given agent. Returns true if a build was running and cancelled. Does not block on teardown — use CancelBuildAndWait when the caller needs the toolserver and DB writes to settle before proceeding.

func (*BuildService) CancelBuildAndWait

func (b *BuildService) CancelBuildAndWait(agentID string, timeout time.Duration) bool

CancelBuildAndWait cancels a running build/upgrade and blocks until the goroutine has run its deferred cleanup (toolserver SIGKILL, DB status write) or until timeout elapses. Returns true if a build was running. Used by Delete to avoid racing the workspace rm and agent-row delete against the upgrade's in-flight writes.

func (*BuildService) CloneRemoteIntoAgent added in v0.4.0

func (b *BuildService) CloneRemoteIntoAgent(ctx context.Context, agentID, remote, branch string, credID pgtype.UUID) error

CloneRemoteIntoAgent clones remote's branch into the agent's repo path so a not-yet-built agent can adopt an existing external codebase (import). It replaces any existing repo dir and verifies the result is a Go project. The caller triggers a SkipScaffold build afterwards to compile the imported HEAD — re-scaffolding would clobber the imported files, exactly as for a clone.

func (*BuildService) Execute added in v0.4.0

func (b *BuildService) Execute(ctx context.Context, plan BuildPlan) (string, error)

Execute is the single pipeline shared by Build, RunUpgrade, and Rollback. Each entry point constructs a BuildPlan and hands it off here; the differences between flows collapse into conditional phases keyed off plan.Kind / plan.Instruction / plan.StartCommit.

Phases:

A.  Lock + per-flow setup (build creates the agent record + schema;
    upgrade/rollback acquire the upgrade lock and load the agent).
A4. Insert the agent_builds row so the "started" event carries its id.
B.  Reposition the repo if StartCommit is set (rollback only): save
    current HEAD as PreserveBranch, then git reset --hard.
C.  Codegen if Instruction is non-empty: run Sol on a working
    branch, commit, merge back.
D.  Build the docker image at current HEAD.
E.  Validate migrations on a schema clone — up→down→up for
    build/upgrade; the rollback-specific down-to dry run is wired in
    by Phase 4 of the plan and lives in a sibling helper.
F.  Swap the agent container (stop old if present, start new).
G/H. Update agents.{source_ref,image_ref,status} and complete the
    agent_builds row.

Returns the agent-builder's exit-tool message (when Sol ran) so the caller can plumb it into the originating conversation. Empty string is fine when no Sol ran.

func (*BuildService) InspectRemote added in v0.4.0

func (b *BuildService) InspectRemote(ctx context.Context, remote, branch string, credID pgtype.UUID) (RemoteState, error)

InspectRemote validates that remote is reachable with credID and reports whether it has any branches (empty vs populated) and whether branch exists. Used at git-connect time to (1) fail fast on a bad/expired token or wrong URL instead of silently saving and breaking on the next build, and (2) drive the empty→mirror / non-empty→import decision. Runs ls-remote from a throwaway temp dir — it's a pure remote query and touches no agent repo.

func (*BuildService) PullAgentRepo added in v0.4.0

func (b *BuildService) PullAgentRepo(ctx context.Context, agent dbq.Agent) (string, error)

PullAgentRepo fetches the configured remote branch and fast-forwards the local main to it. Used by the webhook receiver: when a user push is announced, airlock pulls before enqueueing a rebuild so Execute sees the new HEAD. Errors if the local working tree has uncommitted changes (shouldn't happen in steady state).

func (*BuildService) RebuildAllOnSDKChange added in v0.4.0

func (b *BuildService) RebuildAllOnSDKChange(ctx context.Context)

RebuildAllOnSDKChange checks whether the airlock-bundled agentsdk version differs from the last-seen value persisted in system_settings, and if so kicks off a bounded-concurrency rebuild of every active/stopped agent. Designed to run once at airlock startup, after migrations have applied — the comparison itself is cheap, the rebuild only happens on actual drift.

On success (image rebuilt, container restarted, migrations validated), the agent's existing status is preserved. On failure, the agent is transitioned to status=stopped with the build error captured in error_message — the operator decides whether to roll back, run an upgrade-with-description (Sol bridges the SDK gap), or investigate.

A breaking SDK series change (different major, or a different minor while still pre-1.0) routes every agent through codegen with a migration instruction — a bare recompile would fail on the removed API. A patch / rc bump keeps the fast bare-recompile path (no LLM, image-cache hit).

last_seen_sdk_version is updated only AFTER every agent has been processed (regardless of individual successes) so a crash mid-batch re-triggers the same rebuild next boot. This is safe: a successful rebuild is idempotent (same source_ref → same image hash → image build cache hit → near-instant), and unchanged agents short-circuit in Execute's image-build phase.

func (*BuildService) RecoverStuckOperations

func (b *BuildService) RecoverStuckOperations(ctx context.Context) error

RecoverStuckOperations resets any builds or upgrades left in progress after an unclean shutdown. Should be called on startup.

func (*BuildService) RemoteSharesHistory added in v0.4.0

func (b *BuildService) RemoteSharesHistory(ctx context.Context, agentID, remote, branch string, credID pgtype.UUID) (bool, error)

RemoteSharesHistory fetches the remote branch into the agent's repo and reports whether it shares any history with the agent's local HEAD (a common ancestor exists). Used at connect time to tell a same-repo reconnect (safe — the normal push/pull reconciles with no data loss) from a different, unrelated repo (dangerous — on a push conflict the agent resets its main to the remote's unrelated code). The fetch only updates FETCH_HEAD; it never moves a branch.

func (*BuildService) ReposPath added in v0.4.0

func (b *BuildService) ReposPath() string

ReposPath returns the base directory holding per-agent git repos. Each agent's source lives at <ReposPath>/<agentID>/.

func (*BuildService) Rollback added in v0.4.0

func (b *BuildService) Rollback(_ context.Context, in RollbackInput)

Rollback reverses an agent to a previous build's source_ref. Wraps Execute the same way RunUpgrade does — the rollback-specific work (loading the target build, deciding whether an SDK gap needs Sol, recording the pre-rollback branch name) all happens here before the plan is handed to Execute; Execute itself doesn't know about rollback semantics beyond Phase B (reposition repo) and the rollback_target_id on the new agent_builds row.

Synchronous; caller runs in a goroutine. Caller is responsible for AcquireUpgradeLock before calling — same gate as RunUpgrade so concurrent upgrades and rollbacks can't race.

func (*BuildService) RunGitPoll added in v0.4.0

func (b *BuildService) RunGitPoll(ctx context.Context)

RunGitPoll starts the periodic ls-remote loop. Blocks until ctx is cancelled. Runs an immediate poll on startup so connected agents whose remotes advanced while airlock was down are caught without waiting for the first tick.

func (*BuildService) RunUpgrade

func (b *BuildService) RunUpgrade(_ context.Context, input UpgradeInput)

RunUpgrade executes the upgrade pipeline for an agent whose upgrade_status was set to "building" via AcquireUpgradeLock. Thin wrapper over Execute that handles the upgrade-specific outer lifecycle: route Execute's outcome into agents.upgrade_status and post the single conversation message describing the result.

func (*BuildService) SetBuildSystemNotifier added in v0.4.0

func (b *BuildService) SetBuildSystemNotifier(n PostBuildSystemNotifier)

SetBuildSystemNotifier sets the notifier called after an INITIAL build kicked off from a system-agent create_agent tool finishes. Routed by BuildInput.SystemConversationID (system-agent create path only).

func (*BuildService) SetEventPublisher

func (b *BuildService) SetEventPublisher(ep EventPublisher)

SetEventPublisher sets the event publisher for build/upgrade lifecycle events.

func (*BuildService) SetUpgradeNotifier

func (b *BuildService) SetUpgradeNotifier(n PostUpgradeNotifier)

SetUpgradeNotifier sets the notifier called after an upgrade initiated from an agent's web/bridge/A2A conversation finishes.

func (*BuildService) SetUpgradeSystemNotifier added in v0.4.0

func (b *BuildService) SetUpgradeSystemNotifier(n PostUpgradeSystemNotifier)

SetUpgradeSystemNotifier sets the notifier called after an upgrade initiated from a system-agent conversation finishes. Mirrors SetUpgradeNotifier; the builder routes by UpgradeInput's SystemConversationID vs ConversationID (mutually exclusive — see notifyUpgradeOutcome).

func (*BuildService) ValidateRemoteWrite added in v0.4.0

func (b *BuildService) ValidateRemoteWrite(ctx context.Context, remote, branch string, credID pgtype.UUID, empty bool) error

ValidateRemoteWrite verifies that credID can push the selected branch without changing the remote. It clones populated remotes and creates an ephemeral commit for empty remotes, then uses git push --dry-run.

func (*BuildService) WarmBuildCache

func (b *BuildService) WarmBuildCache(ctx context.Context)

WarmBuildCache pre-downloads Go module dependencies so that the first real agent build hits a warm cache. Materializes a real scaffold and runs a full docker build (same Dockerfile.tmpl + go.mod.tmpl as real builds), then removes the throwaway image. The cache mounts persist.

func (*BuildService) WarmRuntimeCaches

func (b *BuildService) WarmRuntimeCaches(ctx context.Context)

WarmRuntimeCaches seeds the named volumes that the build-prompt loop's direct `go mod tidy` / `go build` invocations consume — distinct from the BuildKit cache mount that WarmBuildCache populates. The agent-builder container at runtime sets GOMODCACHE=/tmp/go-mod and GOCACHE=/tmp/go-cache (see container/docker.go) backed by the instance-scoped <instance>-go-mod-cache and <instance>-go-build-cache Docker named volumes. Without this seed, the first build-prompt iteration pays full download cost for ~25 modules in agentsdk+sol's transitive dep tree. The volumes persist across airlock restarts.

type BuilderPromptData

type BuilderPromptData struct {
	HasWebSearch bool
}

BuilderPromptData holds template data for the builder system prompt.

type EventPublisher

type EventPublisher interface {
	PublishBuildEvent(ctx context.Context, agentID, buildID uuid.UUID, status, errMsg, phase string, tasksDone, tasksTotal int32)
	PublishBuildLogLine(ctx context.Context, agentID, buildID uuid.UUID, seq int64, stream, line string)
	PublishBuildTodos(ctx context.Context, buildID uuid.UUID, seq int64, todosJSON []byte)
}

EventPublisher publishes build/upgrade lifecycle events. Implemented by realtime.PubSub via an adapter.

type HousekeepingResult added in v0.4.0

type HousekeepingResult struct {
	DockerfileChanged     bool
	AgentsMDChanged       bool
	GitignoreChanged      bool
	GoModChanged          bool
	NoticesChanged        bool
	GeneratedFilesRemoved []string
}

HousekeepingResult reports what runHousekeeping changed in the agent repo so the caller can decide whether to make a chore commit.

func (HousekeepingResult) Changed added in v0.4.0

func (r HousekeepingResult) Changed() bool

Changed returns true if any airlock-managed source changed.

type LibsPaths

type LibsPaths struct {
	// Owned is the directory containing agentsdk/goai/sol. Either the
	// operator's AGENT_LIBS_PATH (live dev source) or the extracted cache.
	Owned string
	// Ext is the directory containing goose/templ. Always the extracted
	// cache (devs don't edit these).
	Ext string
}

LibsPaths is what callers need to wire up the per-agent docker build's two BuildKit contexts (libs-owned + libs-ext) and the toolserver's bind-mount overlay (Owned).

func EnsureLibs

func EnsureLibs(ctx context.Context, image, explicitPath, cacheDir string, logger *zap.Logger) (LibsPaths, error)

EnsureLibs guarantees that:

  • The agent-builder image's full /libs/* set is materialized on the host under <cacheDir>/<image-id>/, cached by image ID so a tag bump triggers re-extraction.
  • If explicitPath (AGENT_LIBS_PATH) is set, the owned subdirs all exist there. Owned then points at explicitPath; Ext at the cache.
  • If explicitPath is empty (prod), both Owned and Ext point at the cache.

type PostBuildSystemNotifier added in v0.4.0

type PostBuildSystemNotifier interface {
	NotifyBuildComplete(ctx context.Context, agentID, conversationID uuid.UUID, status, message string) error
}

PostBuildSystemNotifier is the initial-build counterpart of PostUpgradeSystemNotifier: called after a build kicked off by a system-agent create_agent tool finishes (success or failure), so the system agent surfaces the outcome and resumes. Only the system-agent create path sets BuildInput.SystemConversationID; the web create path has none and gets no notification (status shows in the build view).

type PostUpgradeNotifier

type PostUpgradeNotifier interface {
	NotifyUpgradeComplete(ctx context.Context, agentID uuid.UUID, conversationID, status, message string) error
}

PostUpgradeNotifier is called after an upgrade finishes (success, failure, or refusal) to post a single message into the originating agent conversation.

status is "success", "error", or "refused". message is the human-readable text to surface — typically sourced from the agent-builder's exit tool on success, the underlying failure reason on error, or the out-of-scope explanation on refused. The notifier posts exactly ONE message and does not trigger a follow-up LLM turn — the agent's own exit-tool summary already describes the outcome, so re-prompting just produces redundant text.

type PostUpgradeSystemNotifier added in v0.4.0

type PostUpgradeSystemNotifier interface {
	NotifyUpgradeComplete(ctx context.Context, agentID, conversationID uuid.UUID, status, message string) error
}

PostUpgradeSystemNotifier is the parallel sink for upgrades initiated from the in-airlock system agent (not from an agent's own conversation). Same status/message contract as PostUpgradeNotifier; the target is the system_conversations.id of the conversation that triggered the upgrade. Exactly one of {ConversationID, SystemConversationID} is set on any given UpgradeInput; the builder picks the notifier accordingly.

type PushConflictError added in v0.4.0

type PushConflictError struct {
	PreservedBranch string
	RemoteBranch    string
}

PushConflictError signals that the codegen commit conflicted with a concurrent user push on the same branch. The conflicting codegen work is preserved on PreservedBranch (e.g. "airlock/upgrade/{runID}") pushed to the remote (and also kept as a local ref). The local main is reset to the remote tip so subsequent builds start clean.

func (*PushConflictError) Error added in v0.4.0

func (e *PushConflictError) Error() string

type RefusedError added in v0.4.0

type RefusedError struct {
	Message string
}

RefusedError signals that the agent-builder declined a request as outside agentsdk's scope (exit status "refused") rather than failing while attempting in-scope work. The pipeline maps it onto a non-alarming "declined" outcome — agent_builds.status="refused", the agent left untouched — instead of a build failure.

func (*RefusedError) Error added in v0.4.0

func (e *RefusedError) Error() string

type RemoteState added in v0.4.0

type RemoteState struct {
	Empty          bool   // the remote advertises no branches at all
	HasBranch      bool   // the requested branch exists on the remote
	HeadSHA        string // tip SHA of the requested branch ("" when absent)
	DefaultBranch  string // the remote's HEAD branch (from the symref), "" if none
	DefaultHeadSHA string // tip SHA of the default branch ("" when unknown)
}

RemoteState summarizes a git remote as seen via ls-remote with a credential.

func (RemoteState) ImportBranch added in v0.4.0

func (s RemoteState) ImportBranch(requested string) string

ImportBranch returns the branch to adopt when importing: the requested one when it exists on the remote, otherwise the remote's default branch. Empty only when the remote has no branch to import.

type RollbackInput added in v0.4.0

type RollbackInput struct {
	AgentID              string
	InitiatorUserID      pgtype.UUID // user who triggered the rollback; attributes codegen spend (falls back to owner)
	BuildID              string
	ConversationID       string
	SystemConversationID string
}

RollbackInput describes a rollback request: the agent whose state we want to move backwards, and the agent_builds row that defines the target (its source_ref becomes main; its image_ref is rebuilt).

ConversationID and SystemConversationID are mutually exclusive: a rollback triggered from a web/bridge/A2A agent conversation sets the former; one triggered from a system-agent conversation sets the latter. The post-build outcome routes to whichever was set — see BuildService.notifyUpgradeOutcome.

type ScaffoldInputs added in v0.4.0

type ScaffoldInputs struct {
	Name            string
	Slug            string
	BuildProviderID pgtype.UUID
	BuildModel      string
}

ScaffoldInputs carries the per-build creation parameters that only the initial-build flow uses. Lives on BuildPlan as an optional pointer so Kind=upgrade / Kind=rollback don't have to pass nil fields, and so Execute's "is this a fresh build?" check is the presence of the pointer instead of a magic string compare.

type SourceLock added in v0.4.0

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

SourceLock serializes source-tree mutations for one agent across replicas.

func (*SourceLock) Unlock added in v0.4.0

func (l *SourceLock) Unlock()

Unlock releases the advisory lock and its dedicated pool connection.

type UpgradeInput

type UpgradeInput struct {
	AgentID              string
	InitiatorUserID      pgtype.UUID // user who triggered the upgrade; attributes codegen spend (falls back to owner)
	RunID                string      // the run that triggered the upgrade
	Reason               string      // "llm_request", "auto_fix", "manual", "source_deploy"
	Description          string      // what to change
	Message              string      // build description persisted without invoking codegen
	ConversationID       string      // conversation that triggered the upgrade (for post-upgrade reply)
	SystemConversationID string      // system-agent conversation that triggered the upgrade (mutually exclusive with ConversationID)
	ErrorMessage         string      // from failed run (auto_fix)
	PanicTrace           string      // from failed run (auto_fix)
	InputPayload         string      // JSON of failed run input (auto_fix)
	Actions              string      // JSON of recorded actions before failure (auto_fix)
	Messages             string      // conversation messages from the failed run
	Logs                 string      // captured log lines from the failed run (auto_fix)
	BuildError           string      // error_message of the agent's most recent failed build
	BuildLog             string      // tail of that build's docker log
}

UpgradeInput describes an upgrade request.

ConversationID and SystemConversationID are mutually exclusive: an upgrade triggered from a web/bridge/A2A agent conversation sets the former; one triggered from a system-agent conversation sets the latter. The post-build outcome is routed to whichever was set — see BuildService.notifyUpgradeOutcome.

Jump to

Keyboard shortcuts

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