Source review and production activation have separate evidence; the status here records what the corresponding live canary actually proved.
Shown at the current implementation head rather than the canary revision: completion now also validates the source Workflow identity and clamps artifact evidence references.
How to read this file
Follow the execution boundary
Click any line section to open its explanation. The complete file stays visible, and the selected explanation opens directly beneath the code it describes.
package temporalbeads
import (
"context"
"errors"
"fmt"
"sync"
"time"
"go.temporal.io/sdk/activity"
"go.temporal.io/sdk/temporal"
)
const (
ExecuteBeadActivityName = "ExecuteBeadActivity"
CheckpointPhaseAttached = "agent-attached"
CheckpointPhaseComplete = "agent-complete"
cancelPropagationTimeout = 10 * time.Second
maxHeartbeatInterval = 5 * time.Second
)
// AgentProgress is a compact checkpoint produced by an attached agent session.
type AgentProgress struct {
SessionID string `json:"session_id"`
Sequence int64 `json:"sequence"`
Phase string `json:"phase"`
ArtifactRefs []ArtifactRef `json:"artifact_refs,omitempty"`
}
// AgentExecutionRequest starts or attaches using the stable claim token.
type AgentExecutionRequest struct {
Event ReadyEvent `json:"event"`
ClaimToken string `json:"claim_token"`
SessionID string `json:"session_id,omitempty"`
ResumeFrom *HeartbeatCheckpoint `json:"resume_from,omitempty"`
}
// AgentExecutionResult contains only a terminal code and artifact references.
type AgentExecutionResult struct {
SessionID string `json:"session_id"`
Outcome Outcome `json:"outcome"`
ArtifactRefs []ArtifactRef `json:"artifact_refs,omitempty"`
}
// AgentCancellation identifies the exact attached session to cancel.
type AgentCancellation struct {
BeadID string `json:"bead_id"`
Generation int64 `json:"generation"`
ClaimToken string `json:"claim_token"`
SessionID string `json:"session_id"`
}
// AgentExecutor owns the nondeterministic start-or-attach session protocol.
type AgentExecutor interface {
ResolveSession(context.Context, AgentExecutionRequest) (string, error)
Execute(
context.Context,
AgentExecutionRequest,
func(AgentProgress) error,
) (AgentExecutionResult, error)
Cancel(context.Context, AgentCancellation) error
}
// ActivityWorker performs the only claim and outcome mutations in the design.
type ActivityWorker struct {
Beads BeadStore
Agent AgentExecutor
}
// ExecuteBead runs or resumes one exact ready generation.Lines 1–70
Define compact Activity contracts
- What this section does
- Models agent progress, execution, and cancellation payloads, defines the AgentExecutor adapter for the external agent process, and binds it with the work store on the ActivityWorker.
- Why it matters for Temporal
- Compact typed payloads keep large prompts, code changes, and artifacts out of Workflow History.
- Typed payloads
- Artifact references
- Adapter boundary
func (w *ActivityWorker) ExecuteBead(
ctx context.Context,
input ActivityInput,
) (ActivityResult, error) {
if err := w.validate(input); err != nil {
return ActivityResult{}, err
}
lease, err := w.claim(ctx, input.Event)
if err != nil {
return ActivityResult{}, err
}
resume, err := activityHeartbeat(ctx, input.Event, lease)
if err != nil {
return ActivityResult{}, err
}
if resume != nil && resume.Phase == CheckpointPhaseComplete {
return w.complete(ctx, input.Event, lease, AgentExecutionResult{
SessionID: resume.SessionID,
Outcome: OutcomeCompleted,
ArtifactRefs: cloneArtifacts(resume.ArtifactRefs),
}, resume.Sequence, false, resume.ArtifactRefsTruncated)
}
execution, err := w.runAgent(ctx, input.Event, lease, resume)
if err != nil {
return ActivityResult{}, err
}
return w.complete(
ctx,
input.Event,
lease,
execution.result,
execution.lastSequence,
true,
false,
)
}
func (w *ActivityWorker) validate(input ActivityInput) error {
if w.Beads == nil {
return invalidActivityConfiguration("beads store is required")
}
if w.Agent == nil {
return invalidActivityConfiguration("agent executor is required")
}
if err := input.Event.Validate(); err != nil {
return temporal.NewNonRetryableApplicationError(
"invalid ready event", "InvalidReadyEvent", err)
}
return nil
}
func (w *ActivityWorker) claim(
ctx context.Context,
event ReadyEvent,
) (ClaimLease, error) {
workflowID, err := WorkflowID(event.CityID, event.RunID, event.BeadID)
if err != nil {
return ClaimLease{}, temporal.NewNonRetryableApplicationError(
"invalid workflow identity", "InvalidWorkflowIdentity", err)
}
lease, err := w.Beads.Claim(ctx, ClaimRequest{
BeadID: event.BeadID, Generation: event.Generation, WorkflowID: workflowID,
})
if err != nil {
return ClaimLease{}, fmt.Errorf("claim ready generation: %w", err)
}
if !lease.Acquired || lease.BeadID != event.BeadID ||
lease.Generation != event.Generation || lease.Token == "" {
return ClaimLease{}, temporal.NewNonRetryableApplicationError(
"stale claim rejected", "StaleClaim", ErrStaleFence)
}
return lease, nil
}
Lines 71–145
Claim the exact generation
- What this section does
- Validates configuration and input, reads any prior heartbeat, claims the work item under the current Workflow identity, and rejects stale fences.
- Why it matters for Temporal
- Activity execution is at least once. The generation and claim token stop a late attempt from writing into a newer run.
- Generation fence
- Heartbeat resume
- Non-retryable errors
type agentExecution struct {
result AgentExecutionResult
lastSequence int64
}
func (w *ActivityWorker) runAgent(
ctx context.Context,
event ReadyEvent,
lease ClaimLease,
resume *HeartbeatCheckpoint,
) (agentExecution, error) {
request := AgentExecutionRequest{
Event: event, ClaimToken: lease.Token, ResumeFrom: cloneCheckpoint(resume),
}
if resume != nil {
request.SessionID = resume.SessionID
} else {
sessionID, err := w.Agent.ResolveSession(ctx, request)
if err != nil {
return agentExecution{}, fmt.Errorf("resolve agent session: %w", err)
}
if err := validateSegment("session id", sessionID); err != nil {
return agentExecution{}, temporal.NewNonRetryableApplicationError(
"agent resolved an invalid session identity", "InvalidAgentSession", err)
}
request.SessionID = sessionID
}
tracker := newHeartbeatTracker(ctx, event, lease, resume, request.SessionID)
stopHeartbeats := startHeartbeatPump(ctx, tracker)
defer stopHeartbeats()
executionDone := make(chan struct{})
cancellationWatchDone := make(chan struct{})
var cancelOnce sync.Once
var cancelErr error
cancelAttached := func() error {
cancelOnce.Do(func() {
cancelErr = w.cancelAttachedSession(
ctx,
event,
lease,
request.SessionID,
)
})
return cancelErr
}
go func() {
defer close(cancellationWatchDone)
select {
case <-ctx.Done():
if errors.Is(ctx.Err(), context.Canceled) {
_ = cancelAttached()
}
case <-executionDone:
}
}()
result, runErr := w.Agent.Execute(ctx, request, tracker.Record)
close(executionDone)
<-cancellationWatchDone
if agentRunCanceled(runErr, ctx.Err()) {
if err := cancelAttached(); err != nil {
return agentExecution{}, err
}
return agentExecution{}, temporal.NewCanceledError("agent execution canceled")
}
if runErr == nil {
attachedSessionID := tracker.SessionID()
if result.SessionID == "" {
result.SessionID = attachedSessionID
}
if attachedSessionID != "" && result.SessionID != attachedSessionID {
return agentExecution{}, temporal.NewNonRetryableApplicationError(
"agent changed attached session identity", "InvalidAgentResult", nil)
}
return agentExecution{result: result, lastSequence: tracker.Sequence()}, nil
}
activity.GetLogger(ctx).Error("agent execution failed", "error", runErr)
failure := AttemptFailure{
BeadID: event.BeadID, Generation: event.Generation, ClaimToken: lease.Token,
Attempt: activity.GetInfo(ctx).Attempt, Code: "agent-execution-failed",
}
if err := w.Beads.RecordAttemptFailure(ctx, failure); err != nil {
return agentExecution{}, fmt.Errorf("record fenced attempt failure: %w", err)
}
return agentExecution{}, temporal.NewApplicationError(
"agent execution failed", "AgentExecutionFailed")
}
Lines 146–232
Start or reattach the agent
- What this section does
- Resolves one session from the claim token, resumes from heartbeat state, streams progress, and records a failed attempt before Temporal retries.
- Why it matters for Temporal
- A retry attaches to the same long-running agent session instead of launching a second agent against the same worktree.
- Start-or-attach
- Attempt record
- Session identity
// agentRunCanceled reports whether a finished agent run must be reported as
// canceled. A run that produced a result never is, even when cancellation
// arrived while the agent was finishing: the work happened, and the generation
// fence on the completion write, not this branch, decides whether the receipt
// may still land. Classifying a finished run as canceled here would discard an
// hour of real agent work that the fence would have accepted.
func agentRunCanceled(runErr error, ctxErr error) bool {
if runErr == nil {
return false
}
return errors.Is(runErr, context.Canceled) ||
errors.Is(ctxErr, context.Canceled)
}
func (w *ActivityWorker) cancelAttachedSession(
ctx context.Context,
event ReadyEvent,
lease ClaimLease,
sessionID string,
) error {
if sessionID == "" {
return temporal.NewNonRetryableApplicationError(
"canceled agent has no attached session", "MissingSessionIdentity", nil)
}
cancelCtx, cancel := context.WithTimeout(
context.WithoutCancel(ctx),
cancelPropagationTimeout,
)
defer cancel()
err := w.Agent.Cancel(cancelCtx, AgentCancellation{
BeadID: event.BeadID, Generation: event.Generation,
ClaimToken: lease.Token, SessionID: sessionID,
})
if err != nil {
return fmt.Errorf("cancel attached agent session: %w", err)
}
return nil
}
func (w *ActivityWorker) complete(
ctx context.Context,
event ReadyEvent,
lease ClaimLease,
result AgentExecutionResult,
lastSequence int64,
recordFinalHeartbeat bool,
artifactRefsTruncated bool,
) (ActivityResult, error) {
if result.Outcome != OutcomeCompleted || result.SessionID == "" {
return ActivityResult{}, temporal.NewNonRetryableApplicationError(
"agent returned an invalid terminal result", "InvalidAgentResult", nil)
}
if err := validateSegment("session id", result.SessionID); err != nil {
return ActivityResult{}, temporal.NewNonRetryableApplicationError(
"agent returned an invalid session identity", "InvalidAgentResult", err)
}
workflowExecution := activity.GetInfo(ctx).WorkflowExecution
if err := validateWorkflowID(
"source workflow id",
workflowExecution.ID,
); err != nil {
return ActivityResult{}, temporal.NewNonRetryableApplicationError(
"activity has no valid source workflow identity",
"InvalidSourceWorkflow",
err,
)
}
if err := validateSegment(
"source workflow run id",
workflowExecution.RunID,
); err != nil {
return ActivityResult{}, temporal.NewNonRetryableApplicationError(
"activity has no valid source workflow run identity",
"InvalidSourceWorkflow",
err,
)
}
artifactRefs := result.ArtifactRefs
artifactOverflow := artifactRefsTruncated
if len(artifactRefs) > MaxOutcomeEvidenceReferences {
artifactOverflow = true
artifactRefs = artifactRefs[:MaxOutcomeEvidenceReferences]
}
for _, artifact := range artifactRefs {
if err := artifact.Validate(); err != nil {
return ActivityResult{}, temporal.NewNonRetryableApplicationError(
"agent returned an invalid artifact reference", "InvalidArtifactRef", err)
}
}
if recordFinalHeartbeat {
checkpoint := HeartbeatCheckpoint{
BeadID: event.BeadID, Generation: event.Generation,
ClaimToken: lease.Token, SessionID: result.SessionID,
Sequence: lastSequence + 1, Phase: CheckpointPhaseComplete,
ArtifactRefs: cloneArtifacts(artifactRefs),
ArtifactRefsTruncated: artifactOverflow,
}
if err := checkpoint.validatePayload(); err != nil {
return ActivityResult{}, temporal.NewNonRetryableApplicationError(
"terminal heartbeat is invalid", "InvalidHeartbeat", err)
}
activity.RecordHeartbeat(ctx, checkpoint)
}
err := w.Beads.Complete(ctx, Completion{
BeadID: event.BeadID, Generation: event.Generation, ClaimToken: lease.Token,
SessionID: result.SessionID, Outcome: result.Outcome,
SourceWorkflowID: workflowExecution.ID,
SourceWorkflowRunID: workflowExecution.RunID,
ArtifactRefs: cloneArtifacts(artifactRefs),
})
if err != nil {
return ActivityResult{}, fmt.Errorf("write fenced completion: %w", err)
}
return ActivityResult{
EventID: event.EventID,
Outcome: string(result.Outcome),
SessionID: result.SessionID,
ArtifactRefs: cloneArtifacts(artifactRefs),
ArtifactRefsTruncated: artifactOverflow,
}, nil
}
func invalidActivityConfiguration(message string) error {
return temporal.NewNonRetryableApplicationError(
message, "InvalidActivityConfiguration", nil)
}
Lines 233–359
Cancel and complete through the fence
- What this section does
- Keeps a result the agent actually produced even when cancellation lands while it finishes, targets the exact session when the run really was canceled, and writes the terminal receipt only when the generation, claim, session, outcome, artifact references, and the calling Workflow identity are all valid.
- Why it matters for Temporal
- Temporal delivers cancellation and retry semantics, while the application decides what a late cancellation means and prevents stale or ambiguous completions from being accepted.
- Exact-session cancellation
- Fenced receipt
- Source Workflow identity
func activityHeartbeat(
ctx context.Context,
event ReadyEvent,
lease ClaimLease,
) (*HeartbeatCheckpoint, error) {
if !activity.HasHeartbeatDetails(ctx) {
return nil, nil
}
var checkpoint HeartbeatCheckpoint
if err := activity.GetHeartbeatDetails(ctx, &checkpoint); err != nil {
return nil, fmt.Errorf("decode Activity heartbeat: %w", err)
}
if checkpoint.BeadID != event.BeadID ||
checkpoint.Generation != event.Generation ||
checkpoint.ClaimToken != lease.Token ||
checkpoint.Sequence < 0 {
return nil, temporal.NewNonRetryableApplicationError(
"heartbeat belongs to a stale claim", "StaleHeartbeat", ErrStaleFence)
}
if err := checkpoint.validatePayload(); err != nil {
return nil, temporal.NewNonRetryableApplicationError(
"heartbeat checkpoint is invalid", "InvalidHeartbeat", err)
}
return &checkpoint, nil
}
Lines 360–385
Recover the last durable checkpoint
- What this section does
- Reads Activity heartbeat details and verifies that the checkpoint belongs to the current bead generation and claim token.
- Why it matters for Temporal
- A new Activity attempt can resume from progress recorded by the Worker that disappeared.
- Heartbeat details
- Stale-checkpoint rejection
type heartbeatTracker struct {
mu sync.Mutex
ctx context.Context
event ReadyEvent
lease ClaimLease
sessionID string
sequence int64
phase string
artifacts []ArtifactRef
}
func newHeartbeatTracker(
ctx context.Context,
event ReadyEvent,
lease ClaimLease,
resume *HeartbeatCheckpoint,
sessionID string,
) *heartbeatTracker {
tracker := &heartbeatTracker{
ctx: ctx, event: event, lease: lease, sessionID: sessionID,
phase: CheckpointPhaseAttached,
}
if resume != nil {
tracker.sessionID = resume.SessionID
tracker.sequence = resume.Sequence
tracker.phase = resume.Phase
tracker.artifacts = cloneArtifacts(resume.ArtifactRefs)
}
return tracker
}
func startHeartbeatPump(ctx context.Context, tracker *heartbeatTracker) func() {
timeout := activity.GetInfo(ctx).HeartbeatTimeout
if timeout <= 0 {
return func() {}
}
interval := timeout / 3
if interval <= 0 {
interval = timeout
}
if interval > maxHeartbeatInterval {
interval = maxHeartbeatInterval
}
tracker.Heartbeat()
stop := make(chan struct{})
done := make(chan struct{})
go func() {
defer close(done)
ticker := time.NewTicker(interval)
defer ticker.Stop()
for {
select {
case <-ticker.C:
tracker.Heartbeat()
case <-ctx.Done():
return
case <-stop:
return
}
}
}()
return func() {
close(stop)
<-done
}
}
func (t *heartbeatTracker) Heartbeat() {
t.mu.Lock()
defer t.mu.Unlock()
activity.RecordHeartbeat(t.ctx, HeartbeatCheckpoint{
BeadID: t.event.BeadID, Generation: t.event.Generation,
ClaimToken: t.lease.Token, SessionID: t.sessionID,
Sequence: t.sequence, Phase: t.phase,
ArtifactRefs: cloneArtifacts(t.artifacts),
})
}
func (t *heartbeatTracker) Record(progress AgentProgress) error {
t.mu.Lock()
defer t.mu.Unlock()
checkpoint := HeartbeatCheckpoint{
BeadID: t.event.BeadID, Generation: t.event.Generation,
ClaimToken: t.lease.Token, SessionID: progress.SessionID,
Sequence: progress.Sequence, Phase: progress.Phase,
ArtifactRefs: cloneArtifacts(progress.ArtifactRefs),
}
if err := checkpoint.validatePayload(); err != nil {
return fmt.Errorf("invalid agent checkpoint: %w", err)
}
if t.sessionID != "" && progress.SessionID != t.sessionID {
return fmt.Errorf("agent checkpoint changed session identity")
}
if progress.Sequence == t.sequence {
if progress.Phase != t.phase ||
!artifactsEqual(progress.ArtifactRefs, t.artifacts) {
return fmt.Errorf("agent checkpoint reused sequence with different content")
}
return nil
}
if progress.Sequence < t.sequence {
return fmt.Errorf("agent checkpoint sequence must increase monotonically")
}
for _, artifact := range progress.ArtifactRefs {
if err := artifact.Validate(); err != nil {
return err
}
}
t.sessionID = progress.SessionID
t.sequence = progress.Sequence
t.phase = progress.Phase
t.artifacts = cloneArtifacts(progress.ArtifactRefs)
activity.RecordHeartbeat(t.ctx, checkpoint)
return nil
}
func (t *heartbeatTracker) SessionID() string {
t.mu.Lock()
defer t.mu.Unlock()
return t.sessionID
}
func (t *heartbeatTracker) Sequence() int64 {
t.mu.Lock()
defer t.mu.Unlock()
return t.sequence
}
func cloneCheckpoint(checkpoint *HeartbeatCheckpoint) *HeartbeatCheckpoint {
if checkpoint == nil {
return nil
}
cloned := *checkpoint
cloned.ArtifactRefs = cloneArtifacts(checkpoint.ArtifactRefs)
return &cloned
}Lines 386–521
Keep progress alive and monotonic
- What this section does
- Runs a bounded heartbeat pump, records agent checkpoints, rejects changed session identity or a reused sequence number carrying different content (an identical duplicate checkpoint is accepted), and copies resume state safely.
- Why it matters for Temporal
- Heartbeat timeout detects Worker loss, and monotonic checkpoints give the retry a compact recovery cursor.
- Heartbeat timeout
- Monotonic sequence
- Progress cursor