workerkit

package
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Jul 23, 2026 License: Apache-2.0 Imports: 17 Imported by: 0

Documentation

Overview

Package workerkit is the headless human-side domain layer of HumanLLM.

It owns the accept/reply/final state machine, transcript and draft state, and continuation parking for tool calls — everything a human-facing UI needs except rendering. UIs (web, terminal, chat integrations) consume the command / notification / snapshot surface and hold no business state of their own.

workerkit deliberately does NOT duplicate transport durability: the Wire port's implementation (the official llm/workerws.Client with its durable Journal, or an in-process llm.WorkerConnection) owns delivery replay across restarts. The StateStore persists only accepted conversations, drafts, and parked continuations; unaccepted inbox items are rebuilt from transport replay. workerkit never executes caller commands and never touches the caller's working tree.

Index

Constants

View Source
const MaxParkedContinuations = 32

MaxParkedContinuations bounds conversations parked on submitted tool calls. The cap is enforced before the tool_calls event is sent, so a full worker fails closed instead of accumulating unresumable state.

Variables

View Source
var (
	ErrInvalidConfig              = errors.New("workerkit: invalid configuration")
	ErrClosed                     = errors.New("workerkit: worker is closed")
	ErrInvalidCommand             = errors.New("workerkit: invalid command")
	ErrUnknownDelivery            = errors.New("workerkit: unknown inbox delivery")
	ErrUnknownConversation        = errors.New("workerkit: unknown conversation")
	ErrConversationTerminal       = errors.New("workerkit: conversation is terminal")
	ErrConversationNotActive      = errors.New("workerkit: conversation is not active")
	ErrConversationNotAbandonable = errors.New("workerkit: conversation has no detached caller")
	ErrTooManyContinuations       = errors.New("workerkit: too many parked continuations")
	ErrNoMirror                   = errors.New("workerkit: no Mirror is configured")
	ErrUnknownChange              = errors.New("workerkit: unknown review change")
	ErrMirrorScopeMismatch        = errors.New("workerkit: mirror scope does not match conversation")
)

Functions

func SessionWorkspaceID

func SessionWorkspaceID(binding SessionBinding) (string, error)

SessionWorkspaceID returns a portable, stable directory name derived from the authenticated conversation identity. Raw harness session identifiers are never used as filesystem names.

Types

type AlertStore

type AlertStore interface {
	SaveAlert(context.Context, Notice) error
	DeleteAlert(context.Context, uint64) error
	ListAlerts(context.Context) ([]Notice, error)
}

AlertStore is the optional durable extension for human-visible notices. Keeping it separate preserves the small StateStore contract for custom embedders, while the reference memory and SQLite implementations provide it so production alerts survive process restart and dismissal is durable.

type Author

type Author string

Author identifies who produced a transcript entry.

const (
	AuthorCaller Author = "caller"
	AuthorHuman  Author = "human"
	AuthorSystem Author = "system"
)

type Change

type Change struct {
	ID          string     `json:"id"`
	WorkspaceID string     `json:"workspace_id"`
	Path        string     `json:"path"`
	Kind        ChangeKind `json:"kind"`
	Diff        string     `json:"diff,omitempty"`
	Warning     string     `json:"warning,omitempty"`
}

Change is one reviewable difference between the human's mirror and the last delivered baseline. Diff is display text; the mirror owns the exact bytes.

type ChangeKind

type ChangeKind string

ChangeKind classifies one reviewed workspace change.

const (
	ChangeCreate ChangeKind = "create"
	ChangeModify ChangeKind = "modify"
	ChangeDelete ChangeKind = "delete"
)

type Config

type Config struct {
	Wire  Wire
	State StateStore
	// Mirror optionally enables the Live Workspace review/delivery surface.
	// Without it, DeliverChanges and DiscardChanges fail with ErrNoMirror.
	Mirror Mirror
	// AutoResponder optionally answers an assignment without human attention:
	// returning (text, true) confirms the assignment and delivers text as the
	// final event, and the item never reaches the inbox. Products use it for
	// caller housekeeping requests (e.g. OpenCode's tool-less title
	// generation). A failed auto-response falls back to the inbox.
	AutoResponder func(llm.WorkerAssignmentDelivery) (string, bool)
	// Clock supplies transcript timestamps. Nil uses time.Now.
	Clock func() time.Time
	// IDs allocates event and delivery identifiers. Nil uses crypto/rand. The
	// returned value must satisfy the transport's stable-key pattern.
	IDs func(kind string) (string, error)
	// Observer receives human-side lifecycle events. Nil is a no-op. Observer
	// failures are isolated by observe.Emit and never change command outcomes.
	Observer observe.Observer
}

Config composes one Worker. Wire and State are required borrowed dependencies: the host keeps ownership and releases them after Shutdown.

type Conversation

type Conversation struct {
	Key         ConversationKey              `json:"key"`
	Phase       Phase                        `json:"phase"`
	Assignment  llm.WorkerAssignmentDelivery `json:"assignment"`
	Transcript  []TranscriptEntry            `json:"transcript"`
	ParkedCalls []llm.ToolCall               `json:"parked_calls,omitempty"`
	// Delivery is the in-flight reviewed batch, settled when every call of the
	// batch returns a successful result.
	Delivery *PendingDelivery `json:"delivery,omitempty"`
	Draft    string           `json:"draft,omitempty"`
	// HumanWorkspace is this conversation's Human-host working directory. It
	// never describes or exposes the Agent user's filesystem.
	HumanWorkspace *HumanWorkspace `json:"human_workspace,omitempty"`
	// CallerGone is an advisory, persisted operator-safety flag. It permits a
	// locally active conversation to be abandoned only after the transport has
	// identified that exact task's caller as detached. Awaiting-caller/results
	// conversations remain abandonable without it.
	CallerGone bool      `json:"caller_gone,omitempty"`
	UpdatedAt  time.Time `json:"updated_at"`
}

Conversation is the persisted record of one accepted task-scoped exchange. Assignment is the current (latest) assignment; events always bind to its identity and lease.

type ConversationKey

type ConversationKey struct {
	Caller llm.CallerID `json:"caller"`
	TaskID llm.TaskID   `json:"task_id"`
}

ConversationKey is the durable identity of one accepted conversation. It is the completion task scope: every assignment of the same caller task resumes the same conversation.

type EntryKind

type EntryKind string

EntryKind classifies one transcript entry.

const (
	EntryText          EntryKind = "text"
	EntryProgress      EntryKind = "progress"
	EntryClarification EntryKind = "clarification"
	EntryFinal         EntryKind = "final"
	EntryToolCalls     EntryKind = "tool_calls"
	EntryToolResult    EntryKind = "tool_result"
	EntryRejected      EntryKind = "rejected"
)

type HumanWorkspace

type HumanWorkspace struct {
	ID        string `json:"id"`
	Path      string `json:"path"`
	Available bool   `json:"available"`
}

HumanWorkspace is one session's Human-owned working directory. ID is safe to expose in reviews and persists across restarts; Path is meaningful only on the Human host.

type InboxItem

type InboxItem struct {
	Delivery llm.WorkerDeliveryID
	Key      ConversationKey
	Tier     llm.CapabilityTier
	Preview  string
	// ToolCount is the number of caller-declared tools. Zero usually marks an
	// auxiliary request (e.g. OpenCode title/summary generation) rather than
	// the main conversation turn.
	ToolCount  int
	ReceivedAt time.Time
}

InboxItem is one assignment awaiting an accept/reject decision. It is not persisted by workerkit; the transport replays unconfirmed assignments.

type MemoryStateStore

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

MemoryStateStore is the reference in-process StateStore. It is the semantic model for the conformance suite and the default for tests and ephemeral embeddings; it does not survive process restart.

func NewMemoryStateStore

func NewMemoryStateStore() (*MemoryStateStore, framework.ReleaseFunc)

NewMemoryStateStore returns an empty store and its release callback.

func (*MemoryStateStore) DeleteAlert

func (store *MemoryStateStore) DeleteAlert(ctx context.Context, seq uint64) error

func (*MemoryStateStore) DeleteConversation

func (store *MemoryStateStore) DeleteConversation(ctx context.Context, key ConversationKey) error

func (*MemoryStateStore) ListAlerts

func (store *MemoryStateStore) ListAlerts(ctx context.Context) ([]Notice, error)

func (*MemoryStateStore) ListConversations

func (store *MemoryStateStore) ListConversations(ctx context.Context) ([]Conversation, error)

func (*MemoryStateStore) SaveAlert

func (store *MemoryStateStore) SaveAlert(ctx context.Context, notice Notice) error

func (*MemoryStateStore) SaveConversation

func (store *MemoryStateStore) SaveConversation(ctx context.Context, conversation Conversation) error

type Mirror

type Mirror interface {
	Reviews() <-chan Review
	Resolve(context.Context, MirrorResolve) ([]llm.ToolCall, error)
	Cancel(context.Context, []string) error
	Settle(context.Context, MirrorSettlement) error
}

Mirror is the Live Workspace port. The mirror owns the human's working copy, change detection, and the byte-exact content behind each change; workerkit owns when changes are delivered and settled:

  • Reviews delivers complete replacement reviews (coalescing is the implementation's choice); workerkit keeps only the latest.
  • Resolve builds native tool calls for selected changes and freezes their exact bytes for the eventual baseline advance, hiding them from review while in flight. If the delivery is never sent, workerkit calls Cancel to return them to review; a crash before the event is durably owned by the transport leaves them for the mirror to reseed into a later review.
  • Cancel returns resolved-but-unsent changes to review without advancing the baseline. It is idempotent and a no-op for unknown ids.
  • Settle(MirrorDelivered) is called only after the caller returned successful results for every call of the batch — never at send time — so the baseline advances exactly with the caller's working tree. Settle(MirrorDiscarded) drops changes without delivery.

A change that was resolved but never settled must reappear in a later review after restart; the mirror, not workerkit, is the durability owner of review state.

type MirrorOutcome

type MirrorOutcome string

MirrorOutcome is the terminal fate of a reviewed change set.

const (
	// MirrorDelivered means the caller agent returned successful tool results
	// for every call built from these changes; the mirror advances its baseline.
	MirrorDelivered MirrorOutcome = "delivered"
	// MirrorDiscarded means the human dropped the changes without delivery.
	MirrorDiscarded MirrorOutcome = "discarded"
)

type MirrorResolve

type MirrorResolve struct {
	ChangeIDs []string
	Tools     []llm.Tool
	// Scope must match the scope bound to this Human mirror.
	Scope WorkspaceScope
	// Workspace identifies the Human-side session directory selected for this
	// conversation. Change paths passed to builders are relative to this
	// directory and therefore relative to the Agent user's logical project.
	Workspace HumanWorkspace
	// HarnessID and HarnessVersion select an exact, versioned native tool
	// contract. A stock multi-harness mirror must fail closed when no matching
	// builder is registered; tool names alone are not sufficient authority.
	HarnessID      string
	HarnessVersion string
}

MirrorResolve asks the mirror to project selected changes onto the caller's declared native tools. Tools are the caller-declared tools of the current assignment; the mirror (or its host-supplied builder) must only produce calls for tools in this list.

type MirrorSettlement

type MirrorSettlement struct {
	ChangeIDs []string
	Outcome   MirrorOutcome
}

MirrorSettlement finalizes a resolved change set. Settle must be idempotent for an exact repeat.

type Notice

type Notice struct {
	Seq       uint64       `json:"seq"`
	At        time.Time    `json:"at"`
	Code      string       `json:"code"`
	Message   string       `json:"message"`
	Caller    llm.CallerID `json:"caller,omitempty"`
	TaskID    llm.TaskID   `json:"task_id,omitempty"`
	RequestID string       `json:"request_id,omitempty"`
}

Notice is a human-visible alert (e.g. a quarantined outbox row whose reply will never be delivered). Seq is stable for dismissal.

func (Notice) Validate

func (notice Notice) Validate() error

Validate checks the portable human-alert contract implemented by optional AlertStores. A notice may be global or identify one exact caller task; a half-populated identity is rejected so safety actions can never target an ambiguous conversation.

type NoticeSource

type NoticeSource interface {
	Notices() <-chan Notice
}

NoticeSource is an optional Wire extension surfacing transport-level alerts a human must see (for example a quarantined durable outbox row). workerkit consumes it when present and shows the notices in State.Alerts.

type PendingDelivery

type PendingDelivery struct {
	ChangeIDs []string `json:"change_ids"`
	CallIDs   []string `json:"call_ids"`
}

PendingDelivery records one in-flight reviewed batch on a conversation: the tool calls sent and the change IDs to settle when every call succeeds.

type Phase

type Phase string

Phase is the human-side lifecycle of one conversation. It mirrors, but does not replace, the core task state machine: the core remains the correctness authority.

const (
	// PhaseActive accepts Reply/Clarify/Final/SubmitToolCalls commands.
	PhaseActive Phase = "active"
	// PhaseAwaitingResults is parked on submitted tool calls; the matching
	// caller continuation resumes it.
	PhaseAwaitingResults Phase = "awaiting_results"
	// PhaseAwaitingCaller follows a clarification; the caller's next request on
	// the same task resumes it.
	PhaseAwaitingCaller Phase = "awaiting_caller"
	// PhaseTerminal follows a final; the conversation accepts no more events.
	PhaseTerminal Phase = "terminal"
)

type Rejection

type Rejection struct {
	Delivery llm.WorkerEventDelivery
	Receipt  llm.WorkerEventReceipt
}

Rejection is the application-facing NACK: the exact event that was rejected together with the deterministic receipt.

type Review

type Review struct {
	Generation uint64   `json:"generation"`
	Changes    []Change `json:"changes"`
}

Review is one complete, self-consistent view of every pending change. Each review replaces the previous one wholesale; Generation increases with every publication.

type SessionBinding

type SessionBinding struct {
	Scope            WorkspaceScope
	HarnessID        string
	HarnessVersion   string
	HarnessSessionID string
}

SessionBinding is the portable identity from which a Human-side session workspace is prepared. It deliberately contains no Agent filesystem path.

type SessionMirror

type SessionMirror interface {
	Mirror
	// PrepareSession binds the session to preferredPath. An empty path selects
	// the implementation's default beneath its Human base workspace. A
	// non-empty path is an explicit Human choice and must already exist.
	PrepareSession(context.Context, SessionBinding, string) (HumanWorkspace, error)
}

SessionMirror is the optional per-conversation workspace extension. The reference filesystem Mirror implements it; custom Mirrors may omit it when they do not expose a Human working directory.

type State

type State struct {
	Inbox         []InboxItem
	Conversations []Conversation
	// Review is the latest complete Live Workspace review, or nil without a
	// configured Mirror (or before its first publication).
	Review *Review
	// Alerts are undismissed human-visible notices, oldest first.
	Alerts []Notice
}

State is a coherent snapshot for UIs. All values are deep copies; mutating them never affects the worker.

type StateStore

type StateStore interface {
	SaveConversation(context.Context, Conversation) error
	DeleteConversation(context.Context, ConversationKey) error
	// ListConversations returns all conversations ordered by Key (caller, then
	// task) for deterministic recovery.
	ListConversations(context.Context) ([]Conversation, error)
}

StateStore persists accepted conversations across worker restarts. It is deliberately tiny: workerkit serializes all access (single writer), records are whole-value upserts, and the store never interprets their content.

Unaccepted inbox items are intentionally NOT stored here: an unconfirmed assignment is replayed by the transport after restart, so persisting it twice would create a second source of truth.

Implementations own independent copies of every value after a successful SaveConversation and return caller-owned copies from ListConversations. Deleting an absent key is a no-op. Implementations must not retry callbacks and must be safe for use from one goroutine at a time.

type TranscriptEntry

type TranscriptEntry struct {
	At         time.Time      `json:"at"`
	Author     Author         `json:"author"`
	Kind       EntryKind      `json:"kind"`
	Text       string         `json:"text,omitempty"`
	ToolCalls  []llm.ToolCall `json:"tool_calls,omitempty"`
	ToolCallID string         `json:"tool_call_id,omitempty"`
	Code       string         `json:"code,omitempty"`
}

TranscriptEntry is display state, not correctness state: the durable outbox and the core's response events remain the correctness record.

type Wire

type Wire interface {
	Assignments() <-chan llm.WorkerAssignmentDelivery
	Rejections() <-chan Rejection
	SendEvent(context.Context, llm.WorkerEventDelivery) error
	ConfirmAssignment(context.Context, llm.WorkerDeliveryID) error
	ConfirmRejection(context.Context, llm.WorkerDeliveryID) error
	Done() <-chan struct{}
	Err() error
}

Wire is the transport port between workerkit and a HumanLLM deployment.

The transport, not workerkit, owns delivery durability: SendEvent returns only after the event is durably owned by the transport (a remote client's Journal/outbox, or a synchronous in-process commit), and the transport replays unconfirmed assignments and unconfirmed rejections after a process restart. workerkit therefore never persists its own copy of in-flight deliveries; duplicating that state would create a second source of truth.

Assignments and Rejections have workerkit as their only receiver. ACKs are silent; failures surface either as a SendEvent error (nothing was accepted) or as an asynchronous Rejection (a deterministic NACK).

func WrapClient

func WrapClient(client *workerws.Client) Wire

WrapClient adapts the official durable remote client to Wire. The client remains caller-owned: shut it down after the workerkit Worker.

func WrapConnection

func WrapConnection(connection llm.WorkerConnection) Wire

WrapConnection adapts an in-process llm.WorkerConnection (an embedded llm.Service worker endpoint) to Wire. CommitEvent settles synchronously: SendEvent returns nil for an ACK, surfaces a deterministic NACK on the Rejections channel, and returns the error for anything indeterminate so the command can be retried with the same event identity.

This adapter has no durable outbox of its own: an in-process worker shares the service's lifetime, and the service redelivers pending assignments on reconnect exactly as it does for remote workers.

type Worker

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

Worker is the headless human-side domain object. All methods are safe for concurrent use; commands are serialized internally.

func Open

func Open(ctx context.Context, config Config) (*Worker, error)

Open validates the configuration, restores accepted conversations from the StateStore, and starts consuming the wire. ctx bounds construction only.

func (*Worker) Abandon

func (worker *Worker) Abandon(ctx context.Context, key ConversationKey) error

Abandon force-terminates a conversation locally, with no wire event, for the case where the caller will not return. After a restart the caller's completion connection is gone (HTTP is volatile), so a recovered active or awaiting_* conversation can no longer be ended the normal way — a Final or reply would NACK ("could not be delivered"). Abandon settles the human-side mirror to terminal and returns any in-flight reviewed changes to review without advancing the baseline. It sends nothing to the caller; if the caller is somehow still waiting it simply times out. A later continuation for the same task enters the inbox as fresh work (handleAssignment treats a terminal conversation as a reusable task id), so nothing is silently lost or resumed.

func (*Worker) Accept

func (worker *Worker) Accept(ctx context.Context, delivery llm.WorkerDeliveryID) (ConversationKey, error)

Accept turns an inbox assignment into an active conversation. The conversation is persisted before the assignment is confirmed: a crash in between replays the assignment, which then re-confirms idempotently.

func (*Worker) Clarify

func (worker *Worker) Clarify(ctx context.Context, key ConversationKey, text string) error

Clarify hands the turn back to the caller with a question; the conversation awaits the caller's next request on the same task.

func (*Worker) DeliverChanges

func (worker *Worker) DeliverChanges(ctx context.Context, key ConversationKey, changeIDs []string) error

DeliverChanges projects reviewed workspace changes onto the caller's declared native tools and sends them as one tool-call batch. The mirror baseline advances only when the caller returns successful results for every call; a failed send or a failed result leaves the changes pending in review.

func (*Worker) DiscardChanges

func (worker *Worker) DiscardChanges(ctx context.Context, changeIDs []string) error

DiscardChanges settles reviewed changes as discarded without delivery.

func (*Worker) DismissAlert

func (worker *Worker) DismissAlert(seq uint64)

DismissAlert removes one notice by its stable sequence number.

func (*Worker) DismissAlertContext

func (worker *Worker) DismissAlertContext(ctx context.Context, seq uint64) error

DismissAlertContext durably dismisses an alert when the configured StateStore implements AlertStore. The in-memory view changes only after the persistence operation succeeds.

func (*Worker) Final

func (worker *Worker) Final(ctx context.Context, key ConversationKey, text string) error

Final ends the conversation.

func (*Worker) Notifications

func (worker *Worker) Notifications() <-chan struct{}

Notifications signals that Snapshot may have changed. It is coalescing: one pending signal covers any number of changes. UIs should re-read Snapshot on every receive.

func (*Worker) Reject

func (worker *Worker) Reject(ctx context.Context, delivery llm.WorkerDeliveryID, reason string) error

Reject sends a terminal rejection event for an inbox assignment and confirms it. No conversation is created.

func (*Worker) Reply

func (worker *Worker) Reply(ctx context.Context, key ConversationKey, text string) error

Reply sends a non-terminal progress segment; the human turn continues.

func (*Worker) SaveDraft

func (worker *Worker) SaveDraft(ctx context.Context, key ConversationKey, draft string) error

SaveDraft persists an unsent reply for a conversation.

func (*Worker) SetHumanWorkspace

func (worker *Worker) SetHumanWorkspace(
	ctx context.Context,
	key ConversationKey,
	path string,
) (HumanWorkspace, error)

SetHumanWorkspace switches one conversation to an existing Human-side project directory. The selection is persisted before it becomes visible. No Agent path is accepted or inferred here.

func (*Worker) Shutdown

func (worker *Worker) Shutdown(ctx context.Context) error

Shutdown stops consuming the wire and waits for the internal loop. It does not shut down the borrowed Wire or StateStore.

func (*Worker) Snapshot

func (worker *Worker) Snapshot() State

Snapshot returns a deep copy of the current state.

func (*Worker) SubmitToolCalls

func (worker *Worker) SubmitToolCalls(ctx context.Context, key ConversationKey, calls []llm.ToolCall) error

SubmitToolCalls asks the caller agent to execute tools and parks the conversation until the caller continues with their results.

type WorkspaceScope

type WorkspaceScope struct {
	Caller       llm.CallerID `json:"caller"`
	WorkspaceKey string       `json:"workspace_key"`
}

WorkspaceScope binds one Human-owned mirror to one authenticated Agent-user workspace. It is deliberately opaque: it routes changes but does not imply that the Human machine can resolve or mount the caller's directory.

func (WorkspaceScope) Validate

func (scope WorkspaceScope) Validate() error

Validate verifies the stable routing identity of a mirror scope.

Directories

Path Synopsis
Package fsmirror is the official filesystem Mirror adapter for workerkit.
Package fsmirror is the official filesystem Mirror adapter for workerkit.
Package sqlite provides the official durable SQLite implementation of the workerkit StateStore port.
Package sqlite provides the official durable SQLite implementation of the workerkit StateStore port.

Jump to

Keyboard shortcuts

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