cloudagents

package
v0.6.1 Latest Latest
Warning

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

Go to latest
Published: Nov 3, 2026 License: Apache-2.0 Imports: 23 Imported by: 0

Documentation

Overview

Package cloudagents governs background coding agents that run on vendor or CI infrastructure rather than a developer machine (ADR-0034): it normalizes tasks from vendor APIs and GitHub webhooks into one inventory mapped to Halos identities, plans and executes kill fan-out (cancel) and over-limit pauses, and turns finished tasks into session ledger records. Rendering is in the workflow subpackage.

Vendor calls go through vendoradmin.Client, so they share its credential rules: token only in the Authorization header and scrubbed from errors, no redirects, https (or loopback http) only. Tests run against httptest fakes.

Index

Constants

View Source
const (
	GitHubBaseURL = vendoradmin.GitHubBaseURL
	CursorBaseURL = "https://api.cursor.com"
)

API base URLs.

View Source
const (
	OpCancel = "cancel" // stop the running task (terminal)
	OpPause  = "pause"  // stop new tasks, reversibly (a human resumes)
)

Operations on a task.

View Source
const (
	ResultDryRun   = "dry-run"
	ResultDone     = "done"
	ResultFailed   = "failed"
	ResultManual   = "manual"    // no API: Manual says what a human does
	ResultNoClient = "no-client" // the vendor API was not configured
)

Results of an Action.

View Source
const (
	ScopeUser       = "user"
	ScopeRing       = "ring"
	ScopeExperiment = "experiment"
)

Kill scopes: what a kill fans out to.

View Source
const (
	AlertDisabled     = "disabled"     // a task of a vendor policy disables
	AlertUngoverned   = "ungoverned"   // a task of a vendor policy does not list
	AlertUnmanaged    = "unmanaged"    // a run of a workflow Halos did not render
	AlertRepo         = "repo"         // a task in a repository not allowed
	AlertModel        = "model"        // a task on a model not allowed
	AlertUnattributed = "unattributed" // a task no identity maps to a Halos user
	AlertConcurrency  = "concurrency"  // more running tasks than maxConcurrent
	AlertBudget       = "budget"       // vendor-reported usage over a budget
)

Alert kinds.

View Source
const (
	StatusQueued    = "queued"
	StatusRunning   = "running"
	StatusCompleted = "completed"
	StatusFailed    = "failed"
	StatusCancelled = "cancelled"
	StatusUnknown   = "unknown"
)

Task statuses, normalized across vendors.

View Source
const (
	SourceGitHubWebhook = "github-webhook"
	SourceGitHubAPI     = "github-api"
	SourceCursorAPI     = "cursor-api"
)

Task sources.

View Source
const MaxWebhookBody = 1 << 20

MaxWebhookBody bounds a webhook delivery GitHub sends (GitHub caps payloads at 25 MB; a workflow_run delivery is a few KiB).

Variables

View Source
var ErrBadSignature = errors.New("webhook signature missing or invalid")

ErrBadSignature means a delivery's X-Hub-Signature-256 is missing or does not match any configured secret.

Functions

func NewClient

func NewClient(vendor, base, token string) (*vendoradmin.Client, error)

NewClient returns a vendor API client ("github" or "cursor") with the credential rules of vendoradmin.Client, or an error for a base URL that is not https (or loopback http). base "" uses the vendor's.

func VerifySignature

func VerifySignature(secrets []string, body []byte, header string) error

VerifySignature checks GitHub's X-Hub-Signature-256 header ("sha256=" + hex HMAC-SHA256 of the raw body) against each secret (two while rotating), in constant time. Per https://docs.github.com/en/webhooks/using-webhooks/validating-webhook-deliveries.

Types

type Action

type Action struct {
	Task Task   `json:"task"`
	Op   string `json:"op"`
	// Call is the vendor API request, or "" when there is none.
	Call   string `json:"call,omitempty"`
	Manual string `json:"manual,omitempty"`
	Result string `json:"result"`
	Error  string `json:"error,omitempty"`
}

Action is one operation on one task, planned and then (unless a dry run) executed.

func Plan

func Plan(tasks []Task, op string) []Action

Plan maps tasks to op's vendor calls. Every action starts as a dry run (or manual, where the vendor has no API).

type Alert

type Alert struct {
	Vendor  string   `json:"vendor"`
	Kind    string   `json:"kind"`
	Key     string   `json:"key,omitempty"` // user:<id>, group:<g>, org, the repo, the model
	Message string   `json:"message"`
	Value   int64    `json:"value,omitempty"`
	Limit   int64    `json:"limit,omitempty"`
	Tasks   []string `json:"tasks"`
	Pause   []string `json:"pause,omitempty"`
}

Alert is one policy violation or over-limit among tasks. Pause lists the tasks to pause when the vendor's onOverLimit is pause (empty otherwise, and for kinds that do not pause).

func Evaluate

func Evaluate(org *policy.Org, tasks []Task, groups Groups, now time.Time) []Alert

Evaluate checks tasks against policy at now: per-task violations (any status), then concurrency (active tasks) and budgets (usage since each budget's window cutoff). Alerts are sorted for stable output.

type Clients

type Clients struct {
	GitHub *vendoradmin.Client
	Cursor *vendoradmin.Client
	// CursorUser is the Halos user the Cursor key belongs to: Cursor lists
	// only the key's own agents and names no user.
	CursorUser string
}

Clients are the vendor APIs a caller was given; a nil client is not read and its actions are not attempted.

func (Clients) Execute

func (c Clients) Execute(ctx context.Context, actions []Action) []Action

Execute sends every planned call. It never resumes or enables anything.

func (Clients) Fetch

func (c Clients) Fetch(ctx context.Context, org *policy.Org, since time.Time) (tasks []Task, warns, errs []string)

Fetch reads the cloud agent tasks created since `since` from every client it has, attributed to Halos users. Each vendor that fails is reported in errs (credential-free) and the others are still read.

type Groups

type Groups func(user string) []string

Groups returns a Halos user's groups (the SCIM directory on halo-server; nil in the CLI, where ring membership then comes from users, percent and default only).

type Task

type Task struct {
	// ID is "<vendor>:<repo or agent>:<run>", stable across sources.
	ID     string `json:"id"`
	Vendor string `json:"vendor"`
	Source string `json:"source"`
	Repo   string `json:"repo,omitempty"` // owner/repo
	// Ref is the vendor object a cancel targets: the Actions run id, or the
	// Cursor agent id. RunRef is Cursor's run id.
	Ref    string `json:"ref"`
	RunRef string `json:"runRef,omitempty"`
	// Workflow is the Actions workflow file (GitHub-hosted vendors).
	Workflow string `json:"workflow,omitempty"`
	// Managed is false for a run of a workflow Halos did not render (a copy
	// of the vendor's quick-setup file): it is a violation, not governed.
	Managed    bool   `json:"managed"`
	VendorUser string `json:"vendorUser,omitempty"`
	// User is the Halos identity (cloudAgents.identities); "" = unattributed.
	User    string    `json:"user,omitempty"`
	Model   string    `json:"model,omitempty"`
	Status  string    `json:"status"`
	Tokens  int64     `json:"tokens,omitempty"`
	URL     string    `json:"url,omitempty"`
	Start   time.Time `json:"start"`
	Updated time.Time `json:"updated"`
	End     time.Time `json:"end,omitzero"`
}

Task is one cloud agent task, normalized. Every string in it came from a vendor and passed safeID or a fixed vocabulary, so it is safe to print and to use as an API path segment.

func ParseGitHubEvent

func ParseGitHubEvent(event string, body []byte) (Task, bool, error)

ParseGitHubEvent normalizes a verified delivery. ok is false for events that are not cloud agent runs (ping, other workflows, other event types); they are acknowledged and dropped. Call VerifySignature first.

func PauseTasks

func PauseTasks(alerts []Alert, tasks []Task) []Task

PauseTasks are the distinct tasks alerts ask to pause, in task order.

func Select

func Select(org *policy.Org, tasks []Task, scope, name string, groups Groups) (hit []Task, unattributed int, err error)

Select returns the active tasks a kill of scope/name reaches: tasks of the user, of every user the ring resolves, or of every user the experiment assigns a variant (internal/assign through policy, never reimplemented). unattributed counts active tasks with no Halos user, which no ring or experiment kill can reach.

func (Task) Active

func (t Task) Active() bool

Active reports whether the task may still be running (and so counts toward concurrency and can be cancelled).

func (*Task) Attribute

func (t *Task) Attribute(c *policy.CloudAgents)

Attribute sets t.User from the policy's identity map. A Cursor task keeps the API key owner it was fetched with.

func (Task) LedgerRecord

func (t Task) LedgerRecord() (ledger.Record, bool)

LedgerRecord is the session ledger record of a finished task (ADR-0031), normalized and redacted under the default policy (repositories counted, not kept). ok is false while the task runs or when it does not normalize.

Directories

Path Synopsis
Package workflow renders the repository files of the workflow-configured cloud agents (ADR-0034): the Claude Code and Gemini CLI GitHub Actions and Copilot's copilot-setup-steps.yml, SHA-pinned and least-privilege, and checks every rendered file against the security invariants.
Package workflow renders the repository files of the workflow-configured cloud agents (ADR-0034): the Claude Code and Gemini CLI GitHub Actions and Copilot's copilot-setup-steps.yml, SHA-pinned and least-privilege, and checks every rendered file against the security invariants.

Jump to

Keyboard shortcuts

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