Documentation
¶
Overview ¶
Package loops will coordinate shared loop orchestration behavior.
Index ¶
- Constants
- func AppendHumanMessage(metadataJSON *string, m HumanMessage) (string, error)
- func AppendMilestone(metadataJSON *string, m Milestone) (string, error)
- func CanonicalSortedStrings(values []string) []string
- func ClearHITLAsk(metadataJSON *string) (string, error)
- func ClearHumanInbox(metadataJSON *string) (string, error)
- func ClearTakeoverResume(metadataJSON *string) (string, error)
- func ComputeDiscoveryFingerprint(parts ...string) string
- func DebounceSchedule(now time.Time, quietSeconds int, existingAvailableAt time.Time) time.Time
- func IsHardHold(failureKind, resumePolicy string) bool
- func IsManualHoldResumePolicy(resumePolicy string) bool
- func LastFailedDiscoveryFingerprint(metadataJSON *string) string
- func LockLoopRequeue(loopID string) func()
- func LockLoopTarget(key string) func()
- func LoopTargetGuardKey(projectID, loopType, targetType, targetKey string) string
- func LoopTargetGuardKeyFromRecord(loop storage.LoopRecord) string
- func MaxTime(left, right time.Time) time.Time
- func MergeLastFailedDiscoveryFingerprint(metadataJSON *string, fingerprint string) (string, error)
- func NormalizeResumePolicy(failureKind, resumePolicy string) string
- func PullRequestTargetGuardKey(projectID, repo string, prNumber int64) string
- func ShouldRestartFromDiscover(status, resumePolicy string) bool
- func ShouldSuppressFailedRediscovery(loopStatus, storedFingerprint, currentFingerprint string) bool
- func SuppressesAutonomousRecovery(failureKind, resumePolicy string) bool
- func TargetKeyFromLoopRecord(loop storage.LoopRecord) string
- func WriteHITLAsk(metadataJSON *string, ask HITLAsk) (string, error)
- func WriteTakeoverResume(metadataJSON *string, tr TakeoverResume) (string, error)
- type CreateInput
- type HITLAsk
- type HumanMessage
- type Milestone
- type PauseResult
- type Service
- func (s *Service) Create(ctx context.Context, input CreateInput) (storage.LoopRecord, error)
- func (s *Service) Get(ctx context.Context, id string) (*storage.LoopRecord, error)
- func (s *Service) GetBySeq(ctx context.Context, seq int64) (*storage.LoopRecord, error)
- func (s *Service) List(ctx context.Context) ([]storage.LoopRecord, error)
- func (s *Service) Pause(ctx context.Context, loopID string, reason *string) (PauseResult, error)
- func (s *Service) Resume(ctx context.Context, loopID string) (storage.LoopRecord, error)
- func (s *Service) Terminate(ctx context.Context, loopID string, reason *string) (TerminateResult, error)
- func (s *Service) TransitionStatus(ctx context.Context, loopID string, input TransitionInput) (storage.LoopRecord, error)
- type TakeoverResume
- type TerminateResult
- type TransitionInput
Constants ¶
const ( FailureKindRetryableAfterResume = "retryable_after_resume" FailureKindManualIntervention = "manual_intervention" ResumePolicyAdvanceFromCheckpoint = "advance_from_checkpoint" ResumePolicyManualIntervention = "manual_intervention" ResumePolicyReplayStep = "replay_step" ResumePolicyRestartFromDiscover = "restart_from_discover" )
const DiscoveryFingerprintVersion = "v1"
DiscoveryFingerprintVersion is bumped whenever the canonical discovery fingerprint payload changes shape so old persisted fingerprints become non-comparable instead of silently mismatching.
Variables ¶
This section is empty.
Functions ¶
func AppendHumanMessage ¶ added in v0.10.0
func AppendHumanMessage(metadataJSON *string, m HumanMessage) (string, error)
AppendHumanMessage queues one human message (trimming to the most recent humanInboxCap), preserving all other metadata keys.
func AppendMilestone ¶ added in v0.10.0
AppendMilestone adds one milestone to a loop's metadata (trimming to the most recent milestonesCap), preserving all other keys, and returns the updated JSON. A milestone whose text duplicates the immediately-preceding one is dropped so a retried event doesn't stutter the log.
func CanonicalSortedStrings ¶ added in v0.7.2
CanonicalSortedStrings returns a copy of values trimmed, lowercased, sorted, and deduplicated. Used to normalize labels/assignees before fingerprinting.
func ClearHITLAsk ¶ added in v0.10.0
ClearHITLAsk removes the HITL ask state from a loop's metadata JSON.
func ClearHumanInbox ¶ added in v0.10.0
ClearHumanInbox drops all queued human messages (called after the agent drains them in a turn).
func ClearTakeoverResume ¶ added in v0.10.0
ClearTakeoverResume removes the takeover-resume marker from a loop's metadata.
func ComputeDiscoveryFingerprint ¶ added in v0.7.2
ComputeDiscoveryFingerprint produces a stable hash over the supplied parts. Input order is preserved so callers must pass a canonical, role-specific ordering. Slice values are sorted in place by the caller before hashing.
func DebounceSchedule ¶ added in v0.11.9
DebounceSchedule computes the next eligible time for quiet-period debounce.
Quiet period settles new actionable signals before work starts:
- quietSeconds <= 0 → eligible immediately (now)
- otherwise next = max(existingAvailableAt, now+quiet)
Existing AvailableAt is never shortened by debounce (extend/reset only). Retry backoff and no-op follow-up delays remain separate constraints; callers that need both should take the max of DebounceSchedule and those times.
func IsHardHold ¶ added in v0.7.2
func IsManualHoldResumePolicy ¶ added in v0.7.2
func LastFailedDiscoveryFingerprint ¶ added in v0.7.2
LastFailedDiscoveryFingerprint returns the persisted fingerprint of the last terminal-failed discovery attempt, or empty string if none. metadataJSON may be nil.
func LockLoopRequeue ¶ added in v0.11.0
func LockLoopRequeue(loopID string) func()
LockLoopRequeue acquires the process-wide per-loop requeue mutex shared by:
- API retry/start/reuse
- runtime HITL free-text / answer requeues
- runtime deferred/startup recovery requeues
- reviewer/fixer discovery enqueue for an existing loop
Callers must unlock via the returned function (typically defer). Nested acquisitions on the same loop from the same goroutine deadlock — do not call requeue helpers while already holding this lock for that loopID. Call order with LockLoopTarget: take the per-loop lock first, then the target lock.
func LockLoopTarget ¶ added in v0.11.0
func LockLoopTarget(key string) func()
LockLoopTarget acquires the process-wide same-target mutex. An empty key is a no-op. Callers that also take LockLoopRequeue must acquire the per-loop lock first to avoid deadlocks with API discard+retry.
func LoopTargetGuardKey ¶ added in v0.11.0
LoopTargetGuardKey builds the process-wide target mutex key shared by API discard/retry/start/create and runtime recovery/discovery/HITL requeues.
Empty string means no lock (concurrent project-scoped workers are exempt). Pull-request targets omit loop type so fixer/reviewer/worker share one key — they share the managed PR worktree (looper-fix-<project>-pr-N).
func LoopTargetGuardKeyFromRecord ¶ added in v0.11.0
func LoopTargetGuardKeyFromRecord(loop storage.LoopRecord) string
LoopTargetGuardKeyFromRecord derives LoopTargetGuardKey from a stored loop. Key formatting matches API loopTargetKeyFromRecordCompat / loopTargetKeyCompat so API and runtime share the same mutex entries.
func MaxTime ¶ added in v0.11.9
MaxTime returns the later of left and right, treating zero as absent.
func MergeLastFailedDiscoveryFingerprint ¶ added in v0.7.2
MergeLastFailedDiscoveryFingerprint returns a JSON object encoded as a string containing the existing metadata merged with the supplied fingerprint under the autonomousRecovery namespace. The fingerprint may be empty, in which case the field is cleared so the next discovery is allowed to revive the loop.
func NormalizeResumePolicy ¶ added in v0.7.2
func PullRequestTargetGuardKey ¶ added in v0.11.0
PullRequestTargetGuardKey builds the shared PR worktree target mutex key from project + repo + PR number (loop type omitted). Use when discovery creates or requeues a reviewer/fixer for a PR before a loop record is available.
func ShouldRestartFromDiscover ¶ added in v0.7.2
func ShouldSuppressFailedRediscovery ¶ added in v0.7.2
ShouldSuppressFailedRediscovery reports whether autonomous discovery should leave a previously-failed loop in failed state instead of reviving it.
The contract is: suppress only when (a) loop is currently failed, (b) we have a recorded last-failed fingerprint, and (c) the current discovery fingerprint matches it exactly.
All other cases (no stored fingerprint, status not failed, mismatched fingerprint) return false so the loop is revived as before.
func SuppressesAutonomousRecovery ¶ added in v0.7.2
func TargetKeyFromLoopRecord ¶ added in v0.11.0
func TargetKeyFromLoopRecord(loop storage.LoopRecord) string
TargetKeyFromLoopRecord returns the canonical target key for a stored loop (project:/issue:/pull_request:...), matching API loopTargetKeyFromRecordCompat.
func WriteHITLAsk ¶ added in v0.10.0
WriteHITLAsk merges the HITL ask state into a loop's metadata JSON, preserving all other keys, and returns the updated JSON string.
func WriteTakeoverResume ¶ added in v0.10.0
func WriteTakeoverResume(metadataJSON *string, tr TakeoverResume) (string, error)
WriteTakeoverResume merges the takeover-resume marker into a loop's metadata, preserving all other keys.
Types ¶
type CreateInput ¶
type CreateInput struct {
ProjectID string
Type domain.LoopType
Target domain.LoopTarget
Status domain.LoopStatus
ConfigJSON *string
MetadataJSON *string
}
type HITLAsk ¶ added in v0.10.0
type HITLAsk struct {
Question string `json:"question,omitempty"`
Options []string `json:"options,omitempty"`
SessionID string `json:"sessionId,omitempty"`
ExecutionID string `json:"executionId,omitempty"`
Vendor string `json:"vendor,omitempty"`
Answer string `json:"answer,omitempty"`
Status string `json:"status,omitempty"` // "awaiting" | "answered" | "consumed"
AskedAt string `json:"askedAt,omitempty"`
AnsweredAt string `json:"answeredAt,omitempty"`
// Transport records how the ask was delivered ("github" | "feishu"). GitHub
// asks carry the PR + ask-comment id so the answer-poll lane can find the human
// reply that came after the ask and resolve/re-request on that PR.
Transport string `json:"transport,omitempty"`
PRNumber int64 `json:"prNumber,omitempty"`
AskCommentID int64 `json:"askCommentId,omitempty"`
// The agent's decision brief — research + recommendation surfaced on the ask
// card so a human can confirm in seconds instead of researching from scratch.
Recommendation string `json:"recommendation,omitempty"`
RecommendedOption string `json:"recommendedOption,omitempty"`
Consequences map[string]string `json:"consequences,omitempty"`
Confidence string `json:"confidence,omitempty"`
}
HITLAsk is the persisted state of a mid-run human-in-the-loop question. It is written by the runner when the agent asks (Question/Options/SessionID), and the answer is filled in by POST /loops/{seq}/respond. On resume the runner reads Answer + SessionID to continue the same agent session.
func ReadHITLAsk ¶ added in v0.10.0
ReadHITLAsk extracts the HITL ask state from a loop's metadata JSON. The second return is false when no HITL state is present.
type HumanMessage ¶ added in v0.10.0
HumanMessage is one free-text message a human sent into a loop's thread at any time — a follow-up question, a clarification, a new instruction — queued until the loop's next turn drains it and feeds it to the agent (same session). Unlike a button-click decision, a message does not by itself resolve a pending ask; the agent reads it in context and decides whether to proceed, answer, or re-ask.
func ReadHumanInbox ¶ added in v0.10.0
func ReadHumanInbox(metadataJSON *string) []HumanMessage
ReadHumanInbox returns a loop's queued human messages in arrival order.
type Milestone ¶ added in v0.10.0
type Milestone struct {
At string `json:"at"` // ISO-8601 timestamp
Text string `json:"text"` // already-formatted, human-facing (lark_md ok)
}
Milestone is one human-scannable event in a loop's story — a decision, a phase completing, a PR opening — timestamped so the anchor reads as a narrative (who decided what, how long each phase took, the PR link) instead of a single current-status line.
func ReadMilestones ¶ added in v0.10.0
ReadMilestones returns a loop's milestone log in chronological order.
type PauseResult ¶
type PauseResult struct {
Loop storage.LoopRecord
CancelledQueueItems int64
}
type Service ¶
func (*Service) Create ¶
func (s *Service) Create(ctx context.Context, input CreateInput) (storage.LoopRecord, error)
func (*Service) TransitionStatus ¶
func (s *Service) TransitionStatus(ctx context.Context, loopID string, input TransitionInput) (storage.LoopRecord, error)
type TakeoverResume ¶ added in v0.10.0
type TakeoverResume struct {
SessionID string `json:"sessionId,omitempty"`
Prompt string `json:"prompt,omitempty"`
}
TakeoverResume records that a loop was handed back after an interactive human takeover, carrying the native session id the human drove so the daemon's next worker run resumes THAT session (seeing the human's turns) rather than starting a fresh one. Consumed after one resume.
func ReadTakeoverResume ¶ added in v0.10.0
func ReadTakeoverResume(metadataJSON *string) (TakeoverResume, bool)
ReadTakeoverResume returns the pending takeover-resume marker, if any.
type TerminateResult ¶ added in v0.9.5
type TerminateResult struct {
Loop storage.LoopRecord
CancelledQueueItems int64
}