Help us shape the future of Go! Take the 2026 Go Developer Survey

runkit

package
v0.3.0 Latest Latest
Warning

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

Go to latest
Published: Nov 3, 2026 License: Apache-2.0 Imports: 7 Imported by: 0

README

RunKit

Approval checkpoints

CheckpointStore persists the host-encrypted state needed to resume an Agent after human approval. runkit treats ApprovalCheckpoint.Ciphertext as opaque: the host encrypts before storing, decrypts only after a successful lease, and owns keys and caller authorization.

pgstore provides PostgreSQL persistence. ApproveAndLease binds the tenant and agent definition hash, records the approving operator/audit reference, and atomically moves one checkpoint from pending to leased. A lease can finish as consumed, failed, rejected, or expired; failed or expired leases are not retried automatically because an external write may already have occurred. Failed leases retain a bounded failure code for operator audit without storing the decrypted checkpoint or a raw runtime error.

Set RUNKIT_POSTGRES_TEST_DSN to run the PostgreSQL integration tests. Point it at a disposable database: migration creates checkpoint tables in that database.

Goagent approval adapter

goagentapproval is the host-side bridge from a paused agentcore.RunCheckpoint to CheckpointStore. The host still authenticates the operator and supplies the encryption implementation; the adapter never owns keys or authorizes a caller.

adapter, err := goagentapproval.New(checkpointStore, hostCipher, agent)
if err != nil {
    return err
}

// After agent.RunDetailed returns agentcore.ErrApprovalPending:
err = adapter.SavePending(ctx, goagentapproval.PendingCheckpoint{
    ID:             checkpointID,
    TenantID:       tenantID,
    DefinitionHash: agentDefinitionHash,
    ExpiresAt:      time.Now().Add(time.Hour),
    Checkpoint:     result.Interruption.Checkpoint,
})

After authenticating an operator, call ApproveAndResume with the approved tool decisions. Every resume reserves a host-generated next checkpoint ID and expiry. If the Agent pauses again, the adapter encrypts and writes that new checkpoint before it consumes the old lease. If writing the new checkpoint, decrypting state, or resuming fails, the old lease becomes terminally failed; it is never automatically replayed.

Reject only records a rejection. It never decrypts or executes the paused Agent state.

Local macOS encryption

For a local macOS host, put the active data key in Keychain and use the versioned AES-256-GCM cipher:

keys, err := approvalcrypto.OpenMacOSKeychainKeyProvider(
    "goagents.host-api.approvals", "local-v1",
)
if err != nil {
    return err
}
cipher, err := approvalcrypto.NewAESGCMCipher(keys)
if err != nil {
    return err
}

The provider generates the active 32-byte key only on its first use and stores it only in macOS Keychain. SQLite receives a versioned envelope with key_id, a key fingerprint, nonce, and ciphertext; the header is authenticated with the ciphertext. The goagent adapter authenticates checkpoint ID, run ID, tenant ID, and definition hash as associated data, so a ciphertext cannot be moved to another approval record or run. If the Keychain item is deleted, the provider generates a new key under the same ID; checkpoints encrypted with the old key then fail with ErrKeyMaterialChanged. Rotate by choosing a new active key ID; retain the old Keychain item until every checkpoint encrypted with it is terminal. Stores drop a checkpoint's ciphertext once it is consumed, rejected, failed, or expired. There is intentionally no file or environment-variable fallback.

runkit is a host-side run store contract for Go agent applications. It keeps agent run records, lifecycle events, terminal summaries, and workflow/task correlation separate from any one runtime.

The core Store contract is DTO-based:

  • RunRecord: run id, workflow id, task id, status, metadata, timestamps.
  • RunEvent: ordered lifecycle events with bounded metadata.
  • TerminalSummary: content ref, token usage, call counts, tools, abort reason.

The core package includes MemoryStore for examples, tests, and prototypes. The sqlitestore package provides SQLite persistence for host-side audit logs.

Use

store := runkit.NewMemoryStore()

err := store.Create(ctx, runkit.RunRecord{
    RunID:      "agent-run-1",
    WorkflowID: "wf-1",
    TaskID:     "task-1",
    Status:     runkit.StatusRunning,
})
if err != nil {
    return err
}

err = store.AppendEvent(ctx, runkit.RunEvent{
    RunID: "agent-run-1",
    Type:  "stage.started",
    Stage: "think",
})

For durable audit storage, open a SQLite store instead:

store, err := sqlitestore.Open("goagents-runs.db")
if err != nil {
    return err
}
defer store.Close()

For goagent, use the adapter sink:

sink := runkit.NewGoagentEventSink(store, func(event agentcore.Event) runkit.RunRecord {
    return runkit.RunRecord{
        RunID:      event.RunID.String(),
        WorkflowID: workflowID,
        TaskID:     taskID,
        Status:     runkit.StatusRunning,
    }
})

agent, err := agentcore.NewAgent(
    agentcore.WithLLM(llm),
    agentcore.WithEventSink(sink),
)

After the run completes, hosts should write a terminal summary:

err = store.Complete(ctx, result.RunID.String(), runkit.TerminalSummary{
    Status:       runkit.StatusSucceeded,
    ContentRef:   "artifact:wf-1:agent-output",
    InputTokens:  result.Usage.InputTokens,
    OutputTokens: result.Usage.OutputTokens,
    LLMCalls:     result.ExecutionSummary.LLMCalls,
    ToolCalls:    result.ExecutionSummary.ToolCalls,
    UsedTools:    result.ExecutionSummary.UsedTools,
})

Boundary

runkit stores refs and bounded metadata. It should not store raw prompts, full model messages, full tool outputs, or large artifacts. Put those payloads in an artifact store and reference them through ContentRef or host metadata.

Verify

go test ./...

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrCheckpointNotFound     = errors.New("approval checkpoint not found")
	ErrCheckpointNotClaimable = errors.New("approval checkpoint not claimable")
	ErrCheckpointLeaseOwner   = errors.New("approval checkpoint lease owner mismatch")
)
View Source
var ErrRunNotFound = errors.New("run not found")

Functions

This section is empty.

Types

type ApprovalAudit

type ApprovalAudit struct {
	ApproverID string
	Approved   bool
	AuditRef   string
	ReasonCode string
	DecidedAt  time.Time
}

ApprovalAudit records the host identity that allowed or rejected a checkpoint.

type ApprovalCheckpoint

type ApprovalCheckpoint struct {
	ID             string
	RunID          string
	TenantID       string
	DefinitionHash string
	Ciphertext     []byte
	Status         CheckpointStatus
	FailureCode    string
	LeaseOwner     string
	LeaseUntil     time.Time
	ExpiresAt      time.Time
	Approval       *ApprovalAudit
	CreatedAt      time.Time
	UpdatedAt      time.Time
}

ApprovalCheckpoint stores opaque host-encrypted state. Runkit never decrypts Ciphertext, and stores drop it once the checkpoint reaches a terminal status (consumed, rejected, failed, or expired): it holds the paused run's tool arguments and is never decrypted again; the approval audit is kept.

type ApprovalLeaseRequest

type ApprovalLeaseRequest struct {
	CheckpointID   string
	TenantID       string
	DefinitionHash string
	ApproverID     string
	AuditRef       string
	ReasonCode     string
	LeaseOwner     string
	LeaseDuration  time.Duration
	Now            time.Time
}

type CheckpointLeaseCompletion

type CheckpointLeaseCompletion struct {
	CheckpointID string
	TenantID     string
	LeaseOwner   string
	FailureCode  string
	Now          time.Time
}

type CheckpointStatus

type CheckpointStatus string
const (
	CheckpointPending  CheckpointStatus = "pending"
	CheckpointLeased   CheckpointStatus = "leased"
	CheckpointConsumed CheckpointStatus = "consumed"
	CheckpointRejected CheckpointStatus = "rejected"
	CheckpointFailed   CheckpointStatus = "failed"
	CheckpointExpired  CheckpointStatus = "expired"
)

type CheckpointStore

type CheckpointStore interface {
	CreateCheckpoint(ctx context.Context, checkpoint ApprovalCheckpoint) error
	GetCheckpoint(ctx context.Context, checkpointID, tenantID string) (ApprovalCheckpoint, error)
	ApproveAndLease(ctx context.Context, request ApprovalLeaseRequest) (ApprovalCheckpoint, error)
	RejectCheckpoint(ctx context.Context, request ApprovalLeaseRequest) error
	CompleteLease(ctx context.Context, completion CheckpointLeaseCompletion) error
	FailLease(ctx context.Context, completion CheckpointLeaseCompletion) error
	ExpireCheckpoints(ctx context.Context, now time.Time) (int, error)
}

CheckpointStore owns atomic state transitions for opaque approval checkpoints.

type GoagentEventSink

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

func NewGoagentEventSink

func NewGoagentEventSink(store Store, builder GoagentRunRecordBuilder) GoagentEventSink

func (GoagentEventSink) Emit

func (s GoagentEventSink) Emit(ctx context.Context, event agentcore.Event) error

type GoagentRunRecordBuilder

type GoagentRunRecordBuilder func(agentcore.Event) RunRecord

type MemoryCheckpointStore

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

func NewMemoryCheckpointStore

func NewMemoryCheckpointStore() *MemoryCheckpointStore

func (*MemoryCheckpointStore) ApproveAndLease

func (*MemoryCheckpointStore) CompleteLease

func (s *MemoryCheckpointStore) CompleteLease(ctx context.Context, completion CheckpointLeaseCompletion) error

func (*MemoryCheckpointStore) CreateCheckpoint

func (s *MemoryCheckpointStore) CreateCheckpoint(ctx context.Context, checkpoint ApprovalCheckpoint) error

func (*MemoryCheckpointStore) ExpireCheckpoints

func (s *MemoryCheckpointStore) ExpireCheckpoints(ctx context.Context, now time.Time) (int, error)

func (*MemoryCheckpointStore) FailLease

func (*MemoryCheckpointStore) FailPendingCheckpoint

func (s *MemoryCheckpointStore) FailPendingCheckpoint(ctx context.Context, request PendingCheckpointFailure) error

func (*MemoryCheckpointStore) GetCheckpoint

func (s *MemoryCheckpointStore) GetCheckpoint(ctx context.Context, checkpointID, tenantID string) (ApprovalCheckpoint, error)

func (*MemoryCheckpointStore) RejectCheckpoint

func (s *MemoryCheckpointStore) RejectCheckpoint(ctx context.Context, request ApprovalLeaseRequest) error

type MemoryStore

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

func NewMemoryStore

func NewMemoryStore() *MemoryStore

func (*MemoryStore) AppendEvent

func (s *MemoryStore) AppendEvent(ctx context.Context, event RunEvent) error

func (*MemoryStore) Complete

func (s *MemoryStore) Complete(ctx context.Context, runID string, summary TerminalSummary) error

func (*MemoryStore) Create

func (s *MemoryStore) Create(ctx context.Context, record RunRecord) error

func (*MemoryStore) Events

func (s *MemoryStore) Events(ctx context.Context, runID string) ([]RunEvent, error)

func (*MemoryStore) FindByWorkflowID

func (s *MemoryStore) FindByWorkflowID(ctx context.Context, workflowID string) ([]RunRecord, error)

func (*MemoryStore) Get

func (s *MemoryStore) Get(ctx context.Context, runID string) (RunRecord, error)

type PendingCheckpointFailure

type PendingCheckpointFailure struct {
	CheckpointID   string
	RunID          string
	TenantID       string
	DefinitionHash string
	FailureCode    string
	Now            time.Time
}

PendingCheckpointFailure identifies one pending checkpoint without exposing or decrypting its opaque payload.

type PendingCheckpointFailureStore

type PendingCheckpointFailureStore interface {
	FailPendingCheckpoint(context.Context, PendingCheckpointFailure) error
}

PendingCheckpointFailureStore is an optional capability for atomically failing an exact pending checkpoint during controlled host shutdown.

type RunEvent

type RunEvent struct {
	RunID      string
	Sequence   int
	Type       string
	Stage      string
	Iteration  int
	Message    string
	Metadata   map[string]any
	RecordedAt time.Time
}

type RunRecord

type RunRecord struct {
	RunID      string
	WorkflowID string
	TaskID     string
	Status     Status
	Summary    TerminalSummary
	Metadata   map[string]any
	CreatedAt  time.Time
	UpdatedAt  time.Time
}

type Status

type Status string
const (
	StatusRunning   Status = "running"
	StatusSucceeded Status = "succeeded"
	StatusFailed    Status = "failed"
)

type Store

type Store interface {
	Create(context.Context, RunRecord) error
	Get(context.Context, string) (RunRecord, error)
	AppendEvent(context.Context, RunEvent) error
	Events(context.Context, string) ([]RunEvent, error)
	Complete(context.Context, string, TerminalSummary) error
	FindByWorkflowID(context.Context, string) ([]RunRecord, error)
}

type TerminalSummary

type TerminalSummary struct {
	Status       Status
	ContentRef   string
	AbortReason  string
	InputTokens  int
	OutputTokens int
	LLMCalls     int
	ToolCalls    int
	UsedTools    []string
}

Directories

Path Synopsis
Package approvalcrypto encrypts host-owned approval checkpoints without knowing their plaintext schema or persistence backend.
Package approvalcrypto encrypts host-owned approval checkpoints without knowing their plaintext schema or persistence backend.
Package goagentapproval connects goagent approval interruptions to runkit's encrypted, single-lease checkpoint store.
Package goagentapproval connects goagent approval interruptions to runkit's encrypted, single-lease checkpoint store.
Package pgstore persists opaque runkit approval checkpoints in PostgreSQL.
Package pgstore persists opaque runkit approval checkpoints in PostgreSQL.
Package sqlitestore implements runkit.Store with SQLite persistence.
Package sqlitestore implements runkit.Store with SQLite persistence.
Package storetest provides reusable conformance tests for runkit.Store.
Package storetest provides reusable conformance tests for runkit.Store.

Jump to

Keyboard shortcuts

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