team

package
v0.13.0 Latest Latest
Warning

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

Go to latest
Published: Sep 4, 2026 License: Apache-2.0 Imports: 5 Imported by: 0

Documentation

Overview

Package team is the DOMAIN coordination substrate for headless agent teams (see docs/adr/0014-agent-teams.md). It holds the pure, in-memory state two or more concurrently-running agent sessions share to coordinate: a roster of members with lifecycle states, a dependency-aware task list members claim and complete, and a mailbox members use to message one another.

It is a domain leaf: it imports only engine/session (for SessionID) and the standard library, and nothing in session imports team, so there is no cycle. It performs NO I/O, spawns NO goroutines, and knows nothing about the Engine or the LLM — the supervisor (engine/agent) drives the running sessions and shares a *Team by reference. Because mecatl teammates are goroutines in one process rather than separate OS processes, this shared-memory aggregate (guarded by a single mutex) replaces the on-disk, file-locked task files a multi-process harness needs.

All mutators are safe for concurrent use; queries return value copies so callers can never mutate aggregate state without going through a method.

Index

Constants

View Source
const (
	// MaxTasks caps the total number of tasks one team may ever create.
	MaxTasks = 512
	// MaxInboxMessages caps the number of queued (undelivered) messages a single
	// member's inbox may hold at once. Draining frees the budget again.
	MaxInboxMessages = 256
	// MaxMembers caps the roster size of one team.
	MaxMembers = 32
	// MaxFindings caps the findings ledger so a runaway member cannot exhaust memory.
	MaxFindings = 512
)

Aggregate resource caps. They bound a single team's coordination state so a runaway (or adversarial) member cannot exhaust process memory by creating unbounded tasks, queuing unbounded messages, or enrolling unbounded members. The model sees a breach as a tool-result error (ErrTooMany*), not a crash.

View Source
const OperatorSender = "operator"

OperatorSender is the reserved sender identity for messages injected by the out-of-band operator (the human/client on the wire, not a roster member). It is the only non-member `from` Send accepts, and no member may be named it: this keeps the message-provenance space partitioned into exactly "a real teammate" and "the operator", so a teammate cannot impersonate the operator and the wire path cannot impersonate a teammate. See Send and AddMember.

Variables

View Source
var (
	// ErrMemberExists is returned by AddMember when name is already taken.
	ErrMemberExists = errors.New("team: member already exists")
	// ErrUnknownMember is returned when a named member is not on the roster.
	ErrUnknownMember = errors.New("team: unknown member")
	// ErrUnknownTask is returned when a task id is not in the task list.
	ErrUnknownTask = errors.New("team: unknown task")
	// ErrTaskNotClaimable is returned by ClaimTask when the task is not pending,
	// is already assigned, or has unmet dependencies.
	ErrTaskNotClaimable = errors.New("team: task not claimable")
	// ErrTaskState is returned by CompleteTask when the task is not in progress
	// or is completed by a member that does not own it.
	ErrTaskState = errors.New("team: illegal task transition")
	// ErrReservedName is returned by AddMember when name is the reserved operator
	// identity (OperatorSender): a member must not be able to be named the operator
	// and thereby impersonate out-of-band operator messages.
	ErrReservedName = errors.New("team: name is reserved")
	// ErrUnknownSender is returned by Send when from is neither a current roster
	// member nor the reserved operator identity: a message's provenance must be a
	// real teammate or the operator, never an arbitrary forged label.
	ErrUnknownSender = errors.New("team: unknown sender")
	// ErrTooManyTasks is returned by CreateTask when the team is at MaxTasks.
	ErrTooManyTasks = errors.New("team: task limit reached")
	// ErrTooManyMessages is returned by Send when the recipient's inbox is at
	// MaxInboxMessages.
	ErrTooManyMessages = errors.New("team: inbox limit reached")
	// ErrTooManyMembers is returned by AddMember when the roster is at MaxMembers.
	ErrTooManyMembers = errors.New("team: member limit reached")
	// ErrTooManyFindings is returned by AppendFinding when the ledger is at MaxFindings.
	ErrTooManyFindings = errors.New("team: findings ledger limit reached")
)

Errors returned by the Team aggregate.

Functions

This section is empty.

Types

type Finding

type Finding struct {
	// Seq is a team-global monotonic sequence number that witnesses append order.
	Seq int
	// Member is the recording member's name (a current roster member).
	Member string
	// Body is the finding text.
	Body string
}

Finding is one member-authored finding recorded to the shared ledger. Member is the recording member's name (authenticated against the roster by AppendFinding); Body is the finding text (UNTRUSTED — member-authored, fenced before it reaches the lead). The ledger is the PRIMARY channel through which the lead's synthesis turn consolidates the team's work into the final report.

type Member

type Member struct {
	// Name is the unique, human-meaningful handle peers address messages to.
	Name string
	// AgentType is the optional agent-definition name this member adopts (its
	// scoped tools / model / prompt); empty for a generic member.
	AgentType string
	// Session is the id of the running session backing this member (empty until
	// the supervisor wires it).
	Session session.SessionID
	// State is the member's lifecycle state.
	State MemberState
}

Member is one participant in a team. The lead is just a Member like any other; the supervisor knows which name is the lead.

type MemberState

type MemberState string

MemberState is the lifecycle state of a teammate (or the lead).

const (
	// MemberSpawning is the initial state before the member's session is wired.
	MemberSpawning MemberState = "spawning"
	// MemberWorking means the member is running a turn or holds a claimed task.
	MemberWorking MemberState = "working"
	// MemberIdle means the member has no work and is parked awaiting a message or
	// a newly-unblocked task.
	MemberIdle MemberState = "idle"
	// MemberStopped is terminal: the member has shut down.
	MemberStopped MemberState = "stopped"
)

type Message

type Message struct {
	// Seq is a team-global monotonic sequence number (delivery/order witness).
	Seq int
	// From is the sender's member name (may be the lead).
	From string
	// To is the recipient's member name.
	To string
	// Body is the message text.
	Body string
}

Message is one mailbox entry from one member to another.

type Task

type Task struct {
	// ID is the stable identifier.
	ID TaskID
	// Description is the work to do (the prompt seed the assignee runs against).
	Description string
	// Deps lists task ids that must complete before this task can be claimed.
	Deps []TaskID
	// Assignee is the member name that claimed the task, or empty if unclaimed.
	Assignee string
	// State is the task lifecycle state.
	State TaskState
}

Task is one unit of work on the shared list. Dependencies are other tasks that must be Completed before this one becomes claimable.

type TaskID

type TaskID string

TaskID identifies a task within a team.

type TaskState

type TaskState string

TaskState is the lifecycle state of a task.

const (
	// TaskPending is unclaimed work (possibly still blocked by dependencies).
	TaskPending TaskState = "pending"
	// TaskInProgress is claimed by a member and being worked.
	TaskInProgress TaskState = "in_progress"
	// TaskCompleted is finished; it unblocks any task that depends on it.
	TaskCompleted TaskState = "completed"
)

type Team

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

Team is the aggregate root for one agent team's coordination state. Construct it with New. All methods are safe for concurrent use.

func New

func New(name string) *Team

New constructs an empty team identified by name.

func (*Team) AddMember

func (t *Team) AddMember(name, agentType string) error

AddMember enrols a new member in MemberSpawning state. It returns ErrMemberExists if name is already taken, ErrReservedName if name is the reserved operator identity (so a member cannot impersonate the operator), and ErrTooManyMembers if the roster is already at MaxMembers.

func (*Team) AppendFinding

func (t *Team) AppendFinding(member, body string) error

AppendFinding records a member-authored finding to the shared ledger. The recording member is authenticated against the roster (ErrUnknownMember otherwise) exactly as Send authenticates a sender — but unlike Send there is no OperatorSender case: only a real roster member records a finding (the operator does not). The ledger is bounded: at MaxFindings the next append returns ErrTooManyFindings, so a runaway member cannot exhaust memory. The Body is UNTRUSTED member content; the supervisor fences it before it reaches the lead.

func (*Team) ClaimNext

func (t *Team) ClaimNext(member string) (Task, bool, error)

ClaimNext atomically claims the first claimable task (in creation order) for member and returns it. It returns ok=false when nothing is currently claimable (all done, all blocked, or all assigned). The member must exist.

func (*Team) ClaimTask

func (t *Team) ClaimTask(id TaskID, member string) error

ClaimTask atomically claims a specific task for member. It returns ErrTaskNotClaimable if the task is not pending, already assigned, or blocked by an incomplete dependency, and ErrUnknownTask / ErrUnknownMember as appropriate.

func (*Team) CompleteTask

func (t *Team) CompleteTask(id TaskID, member string) error

CompleteTask marks an in-progress task completed. It returns ErrTaskState if the task is not in progress, or if member is not the task's assignee. Completing a task may unblock tasks that depend on it (they become claimable).

func (*Team) CreateTask

func (t *Team) CreateTask(description string, deps ...TaskID) (TaskID, error)

CreateTask appends a task with the given description and dependencies. Every dep must already exist (ErrUnknownTask otherwise). It returns ErrTooManyTasks when the team is already at MaxTasks. It returns the new task id.

func (*Team) Drain

func (t *Team) Drain(member string) ([]Message, error)

Drain returns and clears the pending messages for member, in arrival order (at-most-once delivery). It returns ErrUnknownMember if member is not enrolled.

func (*Team) Findings

func (t *Team) Findings() []Finding

Findings returns a copy of the findings ledger in APPEND ORDER (witnessed by each Finding's Seq), matching the copy-on-read discipline of Tasks/Members. Append order is the consistent idiom for "things that happened over time" (like the mailbox Drain's arrival order); the synthesis turn groups by member for readability but does not depend on enrolment order. Finding has no slice fields, so a shallow clone is a deep copy.

func (*Team) InProgressFor

func (t *Team) InProgressFor(member string) bool

InProgressFor reports whether member currently holds at least one in-progress task. The supervisor uses it to avoid auto-claiming a second task for a member that is already working one — bounding a member to a single in-flight claim so a single member cannot drain the whole task list into itself across rounds.

func (*Team) Members

func (t *Team) Members() []Member

Members returns a copy of the roster in join order.

func (*Team) Name

func (t *Team) Name() string

Name returns the team's identifier.

func (*Team) Quiescent

func (t *Team) Quiescent() bool

Quiescent reports whether the team has reached a terminal-or-deadlocked resting point: no task is pending or in progress, every mailbox is empty, and no member is still working or spawning. It is the supervisor's "team done / nobody can make progress" signal. A team with members all Idle but tasks still blocked by an unsatisfiable dependency is NOT quiescent by this definition (a task remains pending), so the supervisor can distinguish genuine completion from a stuck dependency.

func (*Team) ReleaseTasks

func (t *Team) ReleaseTasks(member string)

ReleaseTasks returns every in-progress task assigned to member back to pending and clears its assignee. It is how a stopped member's unfinished work is freed: an in-progress task owned by a member that will never run again would otherwise stay in_progress forever — blocking its dependents and preventing Quiescent from ever holding (the team would dead-spin to its round cap). Completed and pending tasks are untouched; it is a no-op for an unknown member or one holding none.

func (*Team) RemoveMember

func (t *Team) RemoveMember(name string)

RemoveMember drops a member from the roster, clearing its mailbox. It is used to roll back a failed enrolment (e.g. the supervisor rejecting a member after AddMember but before the member is wired). It is a no-op for an unknown member. It does NOT reassign or release tasks the member may hold; a member rolled back during enrolment holds none.

func (*Team) Send

func (t *Team) Send(from, to, body string) error

Send posts a message from one member to another. The recipient must exist (ErrUnknownMember otherwise). The sender's identity is authenticated: from must be either a current roster member or the reserved OperatorSender, else ErrUnknownSender — this prevents a caller from forging a `from` (e.g. impersonating the lead) on the wire path or in a coordination tool. The recipient's inbox is bounded: a queued (undelivered) backlog at MaxInboxMessages returns ErrTooManyMessages. Messages are delivered to the recipient via Drain.

func (*Team) SetMemberSession

func (t *Team) SetMemberSession(name string, sid session.SessionID) error

SetMemberSession records the running session id backing a member.

func (*Team) SetMemberState

func (t *Team) SetMemberState(name string, state MemberState) error

SetMemberState transitions a member to state.

func (*Team) Tasks

func (t *Team) Tasks() []Task

Tasks returns a copy of the task list in creation order.

Jump to

Keyboard shortcuts

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