Help us shape the future of Go! Take the 2026 Go Developer Survey

board

package
v0.57.0 Latest Latest
Warning

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

Go to latest
Published: Nov 1, 2026 License: AGPL-3.0 Imports: 11 Imported by: 0

Documentation

Overview

Package board is the per-project roster of agents the build agent shares with the teammates it spawns.

A row is one agent: its handle, the session it runs in, whether it is a session or a teammate, the handle that spawned it, and its status. The spawned_by edges make the rows a tree — the project's running agents and who spawned whom — which is the whole of what the board is for. There is no post log: agents coordinate by editing files, and a child reports to its parent by finishing.

The package deliberately depends on nothing but database/sql and the project database: the agent loop, the server and the tools all import it, so an import in the other direction would be a cycle. The server wires the behaviour that needs those packages in through function fields.

Index

Constants

View Source
const (
	// UserHandle is the project owner's handle. The user posts nothing to the
	// board, but the reserved name keeps an agent from taking it.
	UserHandle = "user"
	// SystemHandle names board-generated rows. Reserved for the same reason.
	SystemHandle = "system"
)
View Source
const (
	KindSession  = "session"
	KindTeammate = "teammate"
	KindTask     = "task"
)

Handle kinds: a handle is either a session (the build agent, a resumed session), a teammate spawned by one, or a background task child a session's task tool delegated to.

View Source
const (
	StatusIdle    = "idle"
	StatusRunning = "running"
	StatusDone    = "done"
	StatusStopped = "stopped"
	StatusStale   = "stale"
)

Handle statuses. "stale" is set at startup for a handle whose session is no longer alive; "stopped" is a teammate that was stopped deliberately. done, stopped and stale are terminal: nothing will move the row again, and the spawner is owed one report about it.

Variables

View Source
var ErrHandleTaken = errors.New("handle already registered")

ErrHandleTaken reports that a handle is already on the board: RegisterFresh never reuses or overwrites a row, so a fresh registration of a taken name comes back with this instead of an upsert.

View Source
var ReservedHandles = map[string]bool{
	UserHandle:   true,
	SystemHandle: true,
	"board":      true,
}

ReservedHandles may not be registered or spawned: they name the project owner, the board itself, and board-generated entries.

Functions

func NewHandle

func NewHandle(role string) string

NewHandle names a fresh agent on the board: the role plus four random hex characters, so two sessions of one agent in a directory stay distinct.

func Slug

func Slug(name string) string

Slug reduces a free-form name — a teammate's requested name, typically a phrase — to something ValidateHandle accepts.

func ValidateHandle

func ValidateHandle(name string) error

ValidateHandle reports whether name is a handle the board accepts: lowercase letters, digits, dots, underscores and dashes, starting with a letter or digit, at most 32 characters. The reserved handles the user and the board answer to are refused, because accepting one would let an agent impersonate them.

Types

type Handle

type Handle struct {
	ID        string `json:"id"`
	Directory string `json:"directory,omitempty"`
	Handle    string `json:"handle"`
	SessionID string `json:"sessionId,omitempty"`
	Role      string `json:"role,omitempty"`
	Kind      string `json:"kind,omitempty"`
	// SpawnedBy is the parent's handle; empty for a session that joined on its
	// own. It is the tree edge.
	SpawnedBy string    `json:"spawnedBy,omitempty"`
	Status    string    `json:"status"`
	CreatedAt time.Time `json:"createdAt"`
	UpdatedAt time.Time `json:"updatedAt"`
	// contains filtered or unexported fields
}

Handle is one agent on a project's board.

func (Handle) Terminal

func (h Handle) Terminal() bool

Terminal reports whether nothing will move this handle's status again.

func (Handle) Unreported

func (h Handle) Unreported() bool

Unreported reports whether this handle's status is news to its spawner: it is terminal, and it changed after the spawner was last told.

type Store

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

Store is one project directory's board. Every query is scoped to that directory, so a server in one project never sees another's agents.

func Open

func Open(database *db.DB, dir string) *Store

Open returns the board for dir. The schema is created by migration 065 and reshaped by 067, which run when the project database opens.

func (*Store) BySession

func (s *Store) BySession(ctx context.Context, sessionID string) (Handle, error)

BySession returns the handle a session is registered as, or the zero Handle.

func (*Store) Children

func (s *Store) Children(ctx context.Context, parent string) ([]Handle, error)

Children returns the handles spawned by parent, in join order.

func (*Store) Dir

func (s *Store) Dir() string

Dir is the project directory this board covers.

func (*Store) Get

func (s *Store) Get(ctx context.Context, handle string) (Handle, error)

Get returns one handle, or the zero Handle when it is not on the board.

func (*Store) MarkReported

func (s *Store) MarkReported(ctx context.Context, handles []string) error

MarkReported records that the spawner has been told these handles' current status, by moving each row's notified_at up to its updated_at. Stamping the status time rather than the wall clock keeps "reported" and "changed since" comparable even when both land in the same second.

func (*Store) Reconcile

func (s *Store) Reconcile(ctx context.Context, live map[string]bool) error

Reconcile marks handles whose session no longer exists as stale, so the tree does not advertise agents from a previous run as running. live is the set of session ids that still exist in the project.

func (*Store) Register

func (s *Store) Register(ctx context.Context, h Handle) (Handle, error)

Register joins a handle to the board, or refreshes the row of one that is already there. Re-registering keeps the row's id, creation time and reported stamp: the tree edge and the fact that a completion was already reported both outlive a restart.

func (*Store) RegisterFresh

func (s *Store) RegisterFresh(ctx context.Context, h Handle) (Handle, error)

RegisterFresh joins a handle to the board only if nobody holds its name. It is an insert, not the upsert Register is: a fresh registration must never overwrite an existing row, whose session keeps running and whose eventual report would land on the newcomer's row instead. The check and the insert share the store mutex, so a second registration of one name in this process waits and then sees the first one's row rather than racing it; the UNIQUE keys on board_agent still stand behind it, and a UNIQUE violation the driver reports anyway — another process got there first — is mapped to ErrHandleTaken.

func (*Store) Roster

func (s *Store) Roster(ctx context.Context) ([]Handle, error)

Roster returns every handle on the board, in join order.

func (*Store) SetStatus

func (s *Store) SetStatus(ctx context.Context, handle, status string) error

SetStatus records what an agent is doing, which is what the tree shows. A status change is the board's only event, so it wakes any waiter.

func (*Store) SetStatusUnlessTerminal

func (s *Store) SetStatusUnlessTerminal(ctx context.Context, handle, status string) (bool, error)

SetStatusUnlessTerminal records a status the way SetStatus does, but only while the row is not terminal: done, stopped and stale are final, and the spawner is owed exactly one report about them. It reports whether the row moved, which is also whether the caller still owns that report.

The condition lives in the UPDATE rather than in a read-then-write above it: stopping a teammate and the teammate's loop winding up are two goroutines that race by design, and whichever writes second must not overwrite the other. A stop that lands first turns the loop's own completion into a no-op; a completion that lands first leaves the stop nothing to do.

func (*Store) WaitStatus

func (s *Store) WaitStatus(ctx context.Context, handles []string, timeout time.Duration) ([]Handle, error)

WaitStatus blocks until every named handle is terminal (or gone from the roster) or the timeout passes, and returns the rows it watched. It re-reads on every wake, so a handle that finishes between two checks is never missed.

Jump to

Keyboard shortcuts

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