Documentation
¶
Overview ¶
Package builder implements the agent build and upgrade pipeline.
Index ¶
- Constants
- Variables
- func AgentRepoPath(basePath, agentID string) string
- func CleanWorktree(repoPath string) error
- func CommitScaffold(repoPath string, data scaffold.ScaffoldData) (string, error)
- func CommitWorktree(repoPath, message string) (hash string, committed bool, err error)
- func CopyAgentRepo(basePath, srcID, dstID string) error
- func CreateBranch(repoPath, branch string) error
- func CreateUpgradeBranch(repoPath, runID string) error
- func EnsureGitIdentity(repoPath string) error
- func InitAgentRepo(basePath, agentID string) error
- func MaterializeBranch(repoPath, branch, workDir string) error
- func MergeBranch(repoPath, branch string) error
- func MigrationVersionAt(repoPath, commit string) (int, error)
- func RecoverAgentRepo(repoPath string) (recovered bool, err error)
- func RemoveAgentRepo(basePath, agentID string) error
- func ResetHard(repoPath, commit string) error
- func SaveRef(repoPath, refName, commit string) error
- func SyncWorkdirToRepo(workDir, repoPath string, exclude []string) error
- func UpgradeBranchName(runID string) string
- type AutoFixContext
- type BuildInput
- type BuildKind
- type BuildPlan
- type BuildService
- func (b *BuildService) AcquireSourceLock(ctx context.Context, agentID string) (*SourceLock, error)
- func (b *BuildService) AcquireUpgradeLock(ctx context.Context, agentID string) error
- func (b *BuildService) AgentRepoPath(agentID string) string
- func (b *BuildService) Build(_ context.Context, input BuildInput) error
- func (b *BuildService) CancelBuild(agentID string) bool
- func (b *BuildService) CancelBuildAndWait(agentID string, timeout time.Duration) bool
- func (b *BuildService) CloneRemoteIntoAgent(ctx context.Context, agentID, remote, branch string, credID pgtype.UUID) error
- func (b *BuildService) Execute(ctx context.Context, plan BuildPlan) (string, error)
- func (b *BuildService) InspectRemote(ctx context.Context, remote, branch string, credID pgtype.UUID) (RemoteState, error)
- func (b *BuildService) PullAgentRepo(ctx context.Context, agent dbq.Agent) (string, error)
- func (b *BuildService) RebuildAllOnSDKChange(ctx context.Context)
- func (b *BuildService) RecoverStuckOperations(ctx context.Context) error
- func (b *BuildService) RemoteSharesHistory(ctx context.Context, agentID, remote, branch string, credID pgtype.UUID) (bool, error)
- func (b *BuildService) ReposPath() string
- func (b *BuildService) Rollback(_ context.Context, in RollbackInput)
- func (b *BuildService) RunGitPoll(ctx context.Context)
- func (b *BuildService) RunUpgrade(_ context.Context, input UpgradeInput)
- func (b *BuildService) SetBuildSystemNotifier(n PostBuildSystemNotifier)
- func (b *BuildService) SetEventPublisher(ep EventPublisher)
- func (b *BuildService) SetUpgradeNotifier(n PostUpgradeNotifier)
- func (b *BuildService) SetUpgradeSystemNotifier(n PostUpgradeSystemNotifier)
- func (b *BuildService) ValidateRemoteWrite(ctx context.Context, remote, branch string, credID pgtype.UUID, empty bool) error
- func (b *BuildService) WarmBuildCache(ctx context.Context)
- func (b *BuildService) WarmRuntimeCaches(ctx context.Context)
- type BuilderPromptData
- type EventPublisher
- type HousekeepingResult
- type LibsPaths
- type PostBuildSystemNotifier
- type PostUpgradeNotifier
- type PostUpgradeSystemNotifier
- type PushConflictError
- type RefusedError
- type RemoteState
- type RollbackInput
- type ScaffoldInputs
- type SourceLock
- type UpgradeInput
Constants ¶
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 ¶
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.
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
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
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
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
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 ¶
CreateBranch creates a new branch from main in repoPath.
func CreateUpgradeBranch ¶
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
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
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
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 ¶
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
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
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
RemoveAgentRepo deletes the per-agent repo directory entirely. Idempotent — missing dir is not an error.
func ResetHard ¶ added in v0.4.0
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
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
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
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.
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
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
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
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.