Documentation
¶
Overview ¶
Package protocol defines the public convergence types for the Pasture multi-agent orchestration protocol.
These types are consumed by all other packages (formatters, handlers, workflows, audit, ACP adapter). They are designed to be serialization-safe for Temporal's JSONPlainPayloadConverter and standard encoding/json.
JSON tags use camelCase to match Python aura-protocol output format.
Index ¶
Constants ¶
This section is empty.
Variables ¶
var AllAutomatonRoles = []AutomatonRole{ AutomatonRoleNone, AutomatonRoleConstraintChecker, AutomatonRoleTransitionGate, AutomatonRoleHookHandler, AutomatonRoleConsensusReached, AutomatonRoleCreateFollowup, }
AllAutomatonRoles is the ordered slice of all valid AutomatonRole values. Useful for iteration, completeness checks, and parameterised tests.
var AllContextKinds = []ContextKind{ ContextNone, ContextEpoch, ContextSlice, ContextReview, ContextFollowup, ContextGit, ContextSkill, ContextSession, }
AllContextKinds is the ordered slice of all valid ContextKind values.
Useful for parameterised tests and Scenario 10's enum-membership assertion (the test confirms that ResearcherNoteContext is NOT present).
var AllEventTypes = []EventType{ EventPhaseTransition, EventPhaseAdvance, EventVoteRecorded, EventConstraintChecked, EventSliceStarted, EventSliceCompleted, EventSessionRegistered, EventReviewCycleStarted, }
AllEventTypes is the ordered slice of all valid EventType values.
var AllPastureRoles = []PastureRole{ PastureRoleNone, PastureRoleArchitect, PastureRoleSupervisor, PastureRoleWorker, PastureRoleReviewer, }
AllPastureRoles is the ordered slice of all valid PastureRole values.
var AllPhaseIds = []PhaseId{ PhaseRequest, PhaseElicit, PhasePropose, PhaseReview, PhasePlanReview, PhaseRatify, PhaseHandoff, PhaseImplPlan, PhaseWorkerSlices, PhaseCodeReview, PhaseImplUAT, PhaseLanding, PhaseComplete, }
AllPhaseIds is the ordered slice of all valid PhaseId values (pipeline + terminal). Useful for iteration, completeness checks, and building lookup tables.
var DefaultPipeline = Pipeline{ PhaseRequest, PhaseElicit, PhasePropose, PhaseReview, PhasePlanReview, PhaseRatify, PhaseHandoff, PhaseImplPlan, PhaseWorkerSlices, PhaseCodeReview, PhaseImplUAT, PhaseLanding, }
DefaultPipeline is the standard 12-phase pasture protocol pipeline. The index in this slice determines the pX number (0-based index + 1).
Functions ¶
func MustHaveImpl ¶
func MustHaveImpl()
MustHaveImpl panics if RegisterOpenTaskTracker has not yet been called. Call this at program startup (e.g. in a TestMain or an init() of a top-level package) to fail fast with a clear error rather than discovering the missing wiring the first time OpenTaskTracker is called.
Example — add this to your main package or TestMain:
func init() { protocol.MustHaveImpl() }
In-tree callers (cmd/pasture, cmd/pastured, internal/handlers) already import internal/tasks directly and therefore never need this guard. The guard exists for completeness and for any future in-module integration tests that construct main-like binaries without going through the handler layer.
func RegisterOpenTaskTracker ¶
func RegisterOpenTaskTracker(impl func(dbPath string) (TaskTracker, error))
RegisterOpenTaskTracker is called by internal/tasks's init() to wire the constructor implementation. It is exported only so the internal package can assign through it; external packages MUST NOT call it directly (doing so is a programming error and will overwrite the implementation).
This indirection keeps (TaskTracker, OpenTaskTracker) co-located in pkg/protocol (PROPOSAL-2 §7.4) while the body lives in internal/tasks (UAT-1 placement binding). Without it, OpenTaskTracker's body would import internal/tasks — forbidden because internal/tasks already imports pkg/protocol for the TaskTracker type, which would create an import cycle.
See MustHaveImpl for a startup-time guard that panics if this function has not been called.
Types ¶
type AuditEvent ¶
type AuditEvent struct {
EpochId string `json:"epochId"`
Phase PhaseId `json:"phase"`
Role string `json:"role"`
EventType EventType `json:"eventType"`
Payload map[string]any `json:"payload"`
Timestamp time.Time `json:"timestamp"`
}
AuditEvent is a generic audit trail event emitted by epoch workflows and activities. JSON tags use camelCase to match Python aura-protocol output.
type AutomatonRole ¶
type AutomatonRole string
AutomatonRole is the strongly-typed pasture-side category for SoftwareAgent instances that represent rules-based automata (PROPOSAL-2 §7.6, URD R8).
Wire values are stable strings stored in pasture_agent_categories.automaton_role. The enum has exactly 6 values (None + 5 concrete categories); UAT-1 dropped the earlier generic "Derivation" catch-all in favor of the two first-class values ConsensusReached and CreateFollowup.
const ( // AutomatonRoleNone marks a SoftwareAgent that has no pasture-side // automaton role (e.g. the pastured daemon process itself). AutomatonRoleNone AutomatonRole = "None" // AutomatonRoleConstraintChecker is the canonical name for the // constraint-checker automaton (pasture/automaton/check-constraints). AutomatonRoleConstraintChecker AutomatonRole = "ConstraintChecker" // AutomatonRoleTransitionGate covers the 3 transition gate kinds // (consensus, vote-threshold, exit-condition). AutomatonRoleTransitionGate AutomatonRole = "TransitionGate" // AutomatonRoleHookHandler covers all Claude-Code-hook-event handlers // (per Pasture URD D7's hook list). AutomatonRoleHookHandler AutomatonRole = "HookHandler" // AutomatonRoleConsensusReached is the synthesized event emitted when // all reviewers have voted ACCEPT during a phase transition. UAT-1 // promoted this from a Derivation child to a first-class category. AutomatonRoleConsensusReached AutomatonRole = "ConsensusReached" // AutomatonRoleCreateFollowup synthesizes follow-up epics from // PROPOSAL findings during ratification. UAT-1 promoted this to a // first-class category alongside ConsensusReached. AutomatonRoleCreateFollowup AutomatonRole = "CreateFollowup" )
func (AutomatonRole) IsValid ¶
func (r AutomatonRole) IsValid() bool
IsValid reports whether r is a known AutomatonRole value.
Membership is tested via switch (not slice scan) so the compiler can flag missing cases when the enum grows.
func (AutomatonRole) String ¶
func (r AutomatonRole) String() string
String returns the wire-format string value of r.
type Context ¶
type Context struct {
Kind ContextKind `json:"kind"`
ContextId string `json:"contextId"`
}
Context carries a typed (Kind, ContextId) pair as returned by TaskTracker.EventContexts. PROPOSAL-2 §7.5.
ContextId's shape varies per Kind:
- ContextEpoch / ContextSlice / ContextReview / ContextFollowup: a Provenance TaskID string ("namespace--uuid").
- ContextGit: a git commit SHA (or remote ref).
- ContextSkill: a skill run ID.
- ContextSession: a Claude Code session ID.
- ContextNone: unused.
type ContextKind ¶
type ContextKind string
ContextKind enumerates the kinds of context an audit event may attach to.
Wire values are stable strings stored in context_edges.context_kind. The enum has exactly 8 values (None + 7 concrete kinds) — note that PROPOSAL-2 Scenario 10 asserts ResearcherNoteContext is NOT a member.
const ( // ContextNone is the zero value indicating "no context" — used by // callers building empty Context structs and by IsValid() to allow the // zero value through. ContextNone ContextKind = "None" // ContextEpoch attaches an event to an epoch (Provenance TaskID for the // originating REQUEST). Wire format of context_id: "namespace--uuid". ContextEpoch ContextKind = "EpochContext" // ContextSlice attaches an event to an implementation slice (Beads // SLICE-N task ID). ContextSlice ContextKind = "SliceContext" // ContextReview attaches an event to a review cycle. ContextReview ContextKind = "ReviewContext" // ContextFollowup attaches an event to a FOLLOWUP_SLICE-N task. ContextFollowup ContextKind = "FollowupContext" // ContextGit attaches a free-floating git event (commit, push, rebase) // using the commit SHA (or remote ref) as context_id. ContextGit ContextKind = "GitContext" // ContextSkill attaches a /pasture:* skill invocation; context_id is the // skill run ID. ContextSkill ContextKind = "SkillContext" // ContextSession attaches a Claude Code session event; context_id is // the session ID. ContextSession ContextKind = "SessionContext" )
func (ContextKind) IsValid ¶
func (k ContextKind) IsValid() bool
IsValid reports whether k is a known ContextKind value.
Used by Scenario 10 to assert IsValid("ResearcherNoteContext") == false.
func (ContextKind) String ¶
func (k ContextKind) String() string
String returns the wire-format string value of k.
type EventType ¶
type EventType string
EventType classifies an audit event in the dual-write trail.
const ( EventPhaseTransition EventType = "PhaseTransition" EventPhaseAdvance EventType = "PhaseAdvance" EventVoteRecorded EventType = "VoteRecorded" EventConstraintChecked EventType = "ConstraintChecked" EventSliceStarted EventType = "SliceStarted" EventSliceCompleted EventType = "SliceCompleted" EventSessionRegistered EventType = "SessionRegistered" EventReviewCycleStarted EventType = "ReviewCycleStarted" )
type PastureRole ¶
type PastureRole string
PastureRole mirrors the PROV-O Role concept for non-automaton agents (humans and MLAgents acting in epoch roles). PROPOSAL-2 §7.6 / URD R8.
Wire values are stable strings stored in pasture_agent_categories.pasture_role.
const ( // PastureRoleNone marks an agent without a pasture-side role // (e.g. SoftwareAgents whose categorisation is fully captured by // AutomatonRole). PastureRoleNone PastureRole = "None" // PastureRoleArchitect — owns proposal authoring (Phases 3-7). PastureRoleArchitect PastureRole = "Architect" // PastureRoleSupervisor — owns IMPL_PLAN, slice decomposition, // worker dispatch, and code-review coordination (Phases 8-10). PastureRoleSupervisor PastureRole = "Supervisor" // PastureRoleWorker — implements vertical slices in Phase 9. PastureRoleWorker PastureRole = "Worker" // PastureRoleReviewer — performs code/plan reviews (Phases 4, 5, 10). PastureRoleReviewer PastureRole = "Reviewer" )
func (PastureRole) IsValid ¶
func (r PastureRole) IsValid() bool
IsValid reports whether r is a known PastureRole value.
func (PastureRole) String ¶
func (r PastureRole) String() string
String returns the wire-format string value of r.
type PhaseId ¶
type PhaseId string
PhaseId identifies a phase in the 12-phase epoch lifecycle by its name. The position (pX number) is determined by the phase's index in a Pipeline, not by the value of the PhaseId itself.
const ( PhaseRequest PhaseId = "request" PhaseElicit PhaseId = "elicit" PhasePropose PhaseId = "propose" PhaseReview PhaseId = "review" PhasePlanReview PhaseId = "plan-review" PhaseRatify PhaseId = "ratify" PhaseHandoff PhaseId = "handoff" PhaseImplPlan PhaseId = "impl-plan" PhaseWorkerSlices PhaseId = "worker-slices" PhaseCodeReview PhaseId = "code-review" PhaseImplUAT PhaseId = "impl-uat" PhaseLanding PhaseId = "landing" PhaseComplete PhaseId = "complete" )
func ParsePhaseId ¶
ParsePhaseId parses a flexible phase input string into a PhaseId.
Supported formats (case-insensitive):
- Name only: "request", "elicit", "propose", "review", "plan-review", "ratify", "handoff", "impl-plan", "worker-slices", "code-review", "impl-uat", "landing", "complete"
- pX format: "p1", "p2", ..., "p12" (resolved via DefaultPipeline)
- pX-name: "p1-request", "p2-elicit", ... (legacy; name portion used)
- Number only: "1", "2", ..., "12"
Returns an error if the input does not match any known format.
type Pipeline ¶
type Pipeline []PhaseId
Pipeline is an ordered sequence of phases. The 0-based index of a PhaseId in the slice determines its 1-based phase number (pX). PhaseComplete is a terminal state and is NOT included in the pipeline itself.
func (Pipeline) Next ¶
Next returns the next phase after id in the pipeline. Returns PhaseComplete if id is the last phase or not found.
func (Pipeline) PhaseAt ¶
PhaseAt returns the PhaseId at the given 1-based phase number. Returns ("", false) if number is out of range.
func (Pipeline) PhaseNumber ¶
PhaseNumber returns the 1-based phase number for id, or -1 if not found.
type SessionEntry ¶
type SessionEntry struct {
SessionId string `json:"sessionId"`
EntryIndex int `json:"entryIndex"`
Provider string `json:"provider"`
EntryType string `json:"entryType"`
Role string `json:"role"`
TimestampMs *int64 `json:"timestampMs,omitempty"`
ContentPreview *string `json:"contentPreview,omitempty"` // max 500 chars
TokensIn *int `json:"tokensIn,omitempty"`
TokensOut *int `json:"tokensOut,omitempty"`
HasToolUse bool `json:"hasToolUse"`
ToolKind *string `json:"toolKind,omitempty"` // ACP-aligned tool classification
ToolNamesCsv *string `json:"toolNamesCsv,omitempty"` // comma-separated tool names
HasThinking bool `json:"hasThinking"`
IsError bool `json:"isError"`
StopReason *string `json:"stopReason,omitempty"` // ACP per-turn stop reason
RawByteLength *int `json:"rawByteLength,omitempty"` // raw JSON byte count
ToolCallId *string `json:"toolCallId,omitempty"` // MCP correlation
EntryId *string `json:"entryId,omitempty"` // provider-native ID
ParentEntryId *string `json:"parentEntryId,omitempty"` // parent entry link
Depth int `json:"depth"` // 0 = message, 1 = content part
ParentIndex *int `json:"parentIndex,omitempty"` // entryIndex of parent (nil for depth=0)
ToolInput *string `json:"toolInput,omitempty"` // tool_use input JSON
ToolOutput *string `json:"toolOutput,omitempty"` // tool_result output JSON
Extra *string `json:"extra,omitempty"` // JSON overflow for provider-specific data
}
SessionEntry represents a single indexed entry within a session transcript.
Aligned with agent-data-leverage pkg/schema.SessionEntry schema. All optional fields use pointer types to distinguish absent from zero-value. JSON tags use camelCase aligned with the ACP content model and Python output.
Maps 1:1 to a row in the session_entries table in the audit SQLite backend.
type TaskTracker ¶
type TaskTracker interface {
// ─── Embedded: Provenance task CRUD, edges, labels, comments,
// agents (Human/ML/Software), activities. See provenance.Tracker. ───
provenance.Tracker
// RecordEvent persists a single audit event. Returns an error if the
// underlying store is unavailable or the write fails. The caller
// (typically a Temporal activity) is responsible for retry policy.
//
// PROPOSAL-2 §7.11: workflows call this then immediately call
// AttachContext with ContextEpoch. Free-floating events use other
// ContextKind values (Git/Skill/Session).
//
// Note: callers that need the inserted event_id (so they can attach
// context_edges rows in the same logical step) should prefer
// RecordEventReturningId — it bundles the write + id-recovery in a
// single call, removing the post-write SELECT MAX(id) round-trip the
// S9 free-floating helpers had to do as a workaround.
RecordEvent(ctx context.Context, event AuditEvent) error
// RecordEventReturningId persists a single audit event and returns the
// audit_events.id of the just-inserted row. The implementation reads the
// id from sql.Result.LastInsertId on the SAME INSERT statement that wrote
// the row, so the returned id is race-safe under any level of write
// contention — independent of the D11 "low write contention" deployment
// binding. Returns the new id and a nil error on success; on failure
// returns 0 and an actionable *pasterrors.StructuredError.
//
// This is the canonical RecordEvent entry point for workflow activities
// (PROPOSAL-2 §7.11): RecordTransition and RecordAuditEvent call this
// then immediately call AttachContext(eventId, ContextEpoch, epochId)
// to record the event-to-epoch correlation. Free-floating helpers
// (RecordGitEvent / RecordSkillEvent / RecordSessionEvent) also use it
// in place of the older SELECT MAX(id) workaround that this method
// supersedes (Phase 11 R1-B per finding aura-plugins-d1h6y).
//
// Behaviour for non-SQLite trail backends (e.g. *audit.InMemoryAuditTrail
// used in tests): the returned id is a synthetic per-trail monotonic
// counter — it is NOT a real audit_events row id and MUST NOT be
// persisted across processes. The counter is incremented atomically per
// call so concurrent test goroutines always observe distinct ids,
// matching the SQLite trail's per-statement-LastInsertId guarantee.
// AttachContext on an in-memory trail is a no-op anyway (no
// context_edges table backing it), so the synthetic id is only
// meaningful for AttachContext-relative assertions in unit tests that
// exercise the workflow integration path without paying for a real
// SQLite file.
RecordEventReturningId(ctx context.Context, event AuditEvent) (int64, error)
// QueryEvents returns audit events filtered by epoch and (optionally)
// phase / role. Results are returned in chronological order. epochId
// is required and is always part of the WHERE clause.
//
// Note: this is the legacy v1 query path; new callers should prefer
// Timeline(ctx, ContextEpoch, epochId) which uses the context_edges
// JOIN and works for all ContextKind values, not just epoch.
QueryEvents(ctx context.Context, epochId string, phase *PhaseId, role *string) ([]AuditEvent, error)
// RecordSessionEntries persists a batch of SessionEntry records
// atomically (single transaction). Nil or empty slices are no-ops.
RecordSessionEntries(ctx context.Context, entries []SessionEntry) error
// QuerySessionEntries returns all session entries for sessionId in
// insertion order. Returns an empty (non-nil) slice when no entries
// exist for sessionId.
QuerySessionEntries(ctx context.Context, sessionId string) ([]SessionEntry, error)
// SetAgentCategories upserts the (automaton, pasture-role) pair for
// the given agent into pasture_agent_categories. Idempotent: a second
// call with the same id replaces the row. Both AutomatonRole and
// PastureRole MUST be valid enum values (see IsValid); a nil/zero
// value is permitted and stored as the literal "None".
//
// Returns *pasterrors.StructuredError{Category: CategoryStorage} on
// write failure, or {Category: CategoryValidation} if either enum
// value is unknown.
SetAgentCategories(id provenance.AgentID, automaton AutomatonRole, pastureRole PastureRole) error
// AgentCategories returns the (automaton, pasture-role) pair stored
// for id. Returns ("None", "None", nil) if no row exists for id.
AgentCategories(id provenance.AgentID) (AutomatonRole, PastureRole, error)
// AttachContext adds a row to context_edges binding eventId to the
// (kind, contextId) pair. The (event_id, context_kind, context_id)
// triple is the BCNF composite primary key — duplicate inserts are
// idempotent (returns nil; the existing row is preserved).
//
// kind MUST be a valid ContextKind (kind.IsValid()); contextId MUST
// be non-empty. Validation failures return CategoryValidation.
AttachContext(ctx context.Context, eventId int64, kind ContextKind, contextId string) error
// EventContexts returns the typed contexts attached to eventId, in
// insertion order. Returns an empty (non-nil) slice when no edges
// exist for eventId.
EventContexts(ctx context.Context, eventId int64) ([]Context, error)
// Timeline returns all events whose context_edges row matches the
// (kind, contextId) pair, in chronological order. The intended usage:
//
// events := tracker.Timeline(ctx, ContextEpoch, epochId)
// events := tracker.Timeline(ctx, ContextGit, "<sha>")
//
// A nil/empty contextId returns an empty slice (no error).
Timeline(ctx context.Context, kind ContextKind, contextId string) ([]AuditEvent, error)
// Close releases all resources held by the tracker. It is safe to call
// Close multiple times; the second and subsequent calls return nil.
//
// Note: provenance.Tracker also declares Close(), and the embedded
// method satisfies this interface requirement; implementations MUST
// however ensure both subsystems (the provenance.Tracker AND the
// underlying audit.Trail's *sql.DB) are closed exactly once.
Close() error
}
TaskTracker is the unified Pasture workflow-record façade. Implementations wrap a provenance.Tracker (task CRUD, edges, labels, comments, agents, activities) and an audit.Trail (event recording, query, session entries), both opened against the same SQLite file at ~/.local/share/pasture/pasture.db.
The interface adds 6 pasture-only methods on top of the 28 + 4 inherited:
- Agent categorisation (R8): SetAgentCategories, AgentCategories
- Context attachment (R9): AttachContext, EventContexts, Timeline
- Lifecycle: Close (closes both wrapped subsystems exactly once)
The constructor OpenTaskTracker is the supported way to obtain an instance; see its doc comment for error semantics. Callers MUST call Close on the returned tracker.
All methods are safe for concurrent use; the SQLite file is opened in WAL mode with busy_timeout=5000 (PROPOSAL-2 §10.3 / D11 binding). The cross-subsystem race test (BLOCKER B3) in internal/tasks proves this.
func OpenTaskTracker ¶
func OpenTaskTracker(dbPath string) (TaskTracker, error)
OpenTaskTracker opens the unified SQLite database at dbPath and returns a wrapped TaskTracker. See the OpenTaskTracker var doc comment above for the full contract (errors, side effects, lifecycle, wiring requirement).
If the implementation has not been registered (internal/tasks not imported), this function returns a descriptive error. Call MustHaveImpl() at startup to catch this condition at init time rather than at the first call site.