Documentation
¶
Index ¶
- Variables
- func InitInput[I any](req *InitRequest) (I, bool)
- func InitState[S any](res *InitResult) (S, bool)
- func ResultState[S any](res *StepResult) (S, bool)
- func SpawnInput[I any](req *SpawnRequest) (I, bool)
- func StepState[S any](req *StepRequest) (S, bool)
- type Agent
- type AgentName
- type AttemptInfo
- type Await
- type AwaitKey
- type AwaitKind
- type AwaitOption
- type AwaitSpec
- type AwaitStatus
- type ChangeSet
- type ChildResult
- type ClaimHandler
- type ClaimMiddleware
- type ClaimOutcome
- type ClaimRequest
- type Decision
- type DecisionKind
- type EffectContext
- type Event
- type EventID
- type EventQuery
- type EventType
- type Failure
- type FailureCode
- type FinishHandler
- type FinishResult
- type GenerateHandler
- type GenerateMiddleware
- type GenerateOption
- func WithHistory(h *gollem.History) GenerateOption
- func WithLLMOptions(o ...gollem.GenerateOption) GenerateOption
- func WithLLMSessionOptions(o ...gollem.SessionOption) GenerateOption
- func WithRole(r ModelRole) GenerateOption
- func WithSchema(p *gollem.Parameter) GenerateOption
- func WithSystemPrompt(p string) GenerateOption
- func WithTools(tools ...gollem.Tool) GenerateOption
- type GenerateRequest
- type GenerateResult
- type HistoryRef
- type HistoryStore
- type InheritedHistory
- type InitHandler
- type InitMiddleware
- type InitRequest
- type InitResult
- type Kernel
- func (k *Kernel) Cancel(ctx context.Context, pid ProcessID, reason string) error
- func (k *Kernel) GetProcess(ctx context.Context, pid ProcessID) (*Process, error)
- func (k *Kernel) ListAwaits(ctx context.Context, pid ProcessID) ([]*Await, error)
- func (k *Kernel) ListEvents(ctx context.Context, pid ProcessID, opts ...ListEventsOption) ([]*Event, error)
- func (k *Kernel) Respond(ctx context.Context, pid ProcessID, key AwaitKey, response []byte, ...) error
- func (k *Kernel) Serve(ctx context.Context, opts ...ServeOption) error
- type KernelOption
- func WithClaimMiddleware(mw ...ClaimMiddleware) KernelOption
- func WithClock(fn func() time.Time) KernelOption
- func WithGenerateMiddleware(mw ...GenerateMiddleware) KernelOption
- func WithInitMiddleware(mw ...InitMiddleware) KernelOption
- func WithLogger(l *slog.Logger) KernelOption
- func WithModelRole(role ModelRole, client gollem.LLMClient) KernelOption
- func WithSpawnMiddleware(mw ...SpawnMiddleware) KernelOption
- func WithStepMiddleware(mw ...StepMiddleware) KernelOption
- func WithToolCallMiddleware(mw ...ToolCallMiddleware) KernelOption
- func WithToolFactory(f ToolFactory) KernelOption
- type LimitDecision
- type LimitKind
- type Limiter
- type ListEventsOption
- type Metrics
- type ModelRole
- type Process
- type ProcessGuard
- type ProcessID
- type ProcessStatus
- type RegisterOption
- type Registry
- type Repository
- type RespondOption
- type RetryBackoff
- type ServeOption
- func WithLease(d time.Duration) ServeOption
- func WithMaxCancelDeferrals(n int) ServeOption
- func WithMaxConcurrent(n int) ServeOption
- func WithMaxStepAttempts(n int) ServeOption
- func WithMaxStepsPerClaim(n int) ServeOption
- func WithMaxUncleanReclaims(n int) ServeOption
- func WithPollConcurrency(n int) ServeOption
- func WithPollInterval(d time.Duration) ServeOption
- func WithRetryBackoff(fn RetryBackoff) ServeOption
- func WithWorkerID(id string) ServeOption
- type Session
- type SpawnHandler
- type SpawnMiddleware
- type SpawnOption
- type SpawnRequest
- type StepHandler
- type StepMiddleware
- type StepRequest
- type StepResult
- type Strategy
- type StrategyBinding
- type SubjectRef
- type Syscalls
- type ToolCallHandler
- type ToolCallMiddleware
- type ToolCallRequest
- type ToolFactory
Constants ¶
This section is empty.
Variables ¶
var ( // ErrInvalidConfig is returned by New when a required dependency is nil. ErrInvalidConfig = goerr.New("invalid config") // ErrInvalidRequest is returned for a malformed request (wrong await kind, // nil payload, unknown pending child, spawn input type mismatch, ...). ErrInvalidRequest = goerr.New("invalid request") // ErrInvalidAgentDef is returned by Register for a bad agent definition // (empty name, version < 1, duplicate name, nil strategy). ErrInvalidAgentDef = goerr.New("invalid agent definition") // ErrUnknownAgent is returned by Spawn/SpawnChild for an unregistered agent. ErrUnknownAgent = goerr.New("unknown agent") // ErrSubjectBusy is returned by Spawn when an open Process holds the subject. ErrSubjectBusy = goerr.New("subject busy") // ErrProcessNotFound is returned when a Process does not exist. ErrProcessNotFound = goerr.New("process not found") // ErrProcessFinished is returned when acting on a terminal Process. ErrProcessFinished = goerr.New("process finished") // ErrAwaitNotFound is returned when an await row does not exist. ErrAwaitNotFound = goerr.New("await not found") // ErrAwaitClosed is returned when responding to an await that is not open // (first-writer-wins; the second Respond always gets this). ErrAwaitClosed = goerr.New("await closed") // ErrEventNotFound is returned when a ListEvents cursor names an event the // Process does not have. It is the signal that a stored cursor went stale, // which is why it is not silently treated as "start from the beginning". ErrEventNotFound = goerr.New("event not found") // ErrToolNotFound is returned by CallTool for an unknown tool name. ErrToolNotFound = goerr.New("tool not found") // ErrLimitExceeded is returned to a strategy when Limit stops execution // before an effect runs. ErrLimitExceeded = goerr.New("limit exceeded") // ErrConflict is returned by Repository.Apply when a precondition (Rev CAS, // Guard, or uniqueness) is not met; nothing is written. ErrConflict = goerr.New("conflict") // ErrSuspendWithoutAwait is a transition error: a Suspend produced no open // await and no WaitChildren elision (prevents a permanent hang). ErrSuspendWithoutAwait = goerr.New("suspend without await") // ErrRepositoryIndeterminate is returned by the filesystem reference // implementation after a post-rename I/O failure leaves persisted state and // in-memory state diverged; the Repository is fail-stopped until reopened. ErrRepositoryIndeterminate = goerr.New("repository indeterminate") // ErrServeActive is returned by Serve when another Serve is already running on // the same Kernel. One Kernel drives at most one active Serve, because the // eager dispatcher and its concurrency semaphore are installed per-Serve. ErrServeActive = goerr.New("serve already active") // ErrHistoryNotConfigured is returned by every Session method when the agent // was not registered with WithHistoryStore. The managed conversation needs a // store; rather than silently run without persistence, the call fails so the // misconfiguration surfaces (ADR-0017). ErrHistoryNotConfigured = goerr.New("history store not configured") // ErrHistoryVersionMissing is what a HistoryStore returns when Load is given a // ref it cannot resolve. A referenced version that is gone is data loss, not // an empty conversation, so it fails the transition instead of silently // restarting the conversation from nothing. ErrHistoryVersionMissing = goerr.New("history version missing") )
Sentinel errors. Callers discriminate with errors.Is. Handlers wrap these with goerr.Wrap to add context.
Functions ¶
func InitInput ¶
func InitInput[I any](req *InitRequest) (I, bool)
InitInput reads the launch input as I. A middleware runs for every agent, so ok == false simply means "not an agent this middleware knows about" — pass the request to next unchanged rather than treating it as an error.
func InitState ¶
func InitState[S any](res *InitResult) (S, bool)
InitState reads the produced initial state as S.
func ResultState ¶
func ResultState[S any](res *StepResult) (S, bool)
ResultState reads the post-transition state as S.
func SpawnInput ¶
func SpawnInput[I any](req *SpawnRequest) (I, bool)
SpawnInput reads the child's launch input as I.
func StepState ¶
func StepState[S any](req *StepRequest) (S, bool)
StepState reads the transition's input state as S. As with InitInput, ok == false means the request belongs to another agent.
Types ¶
type Agent ¶
type Agent[I any] struct { // contains filtered or unexported fields }
Agent is an opaque handle carrying the launch input type I (the return of Register). It is the only entry point for spawning a Process; the any-typed type erasure is confined to unexported methods (D43).
func Register ¶
func Register[S, I, O any](r *Registry, name AgentName, version int, s Strategy[S, I, O], opts ...RegisterOption[O]) (Agent[I], error)
Register registers a typed strategy and returns a typed handle carrying the input type I (D26/D43: required args are positional; the old AgentDef struct is gone). Empty name, version < 1, nil strategy, a nil completion handler, or a duplicate name yields ErrInvalidAgentDef. Contract: complete all Register calls before Spawn/Serve.
The handle stays Agent[I]: O is consumed only by the completion handler, so carrying it on the handle would make it a phantom type parameter.
func (Agent[I]) Spawn ¶
func (a Agent[I]) Spawn(ctx context.Context, k *Kernel, input I, opts ...SpawnOption) (ProcessID, error)
Spawn launches a Process from the application (typed; input is checked at compile time). It creates the Process pending and returns its ID immediately (asynchronous launch; execution starts at a worker's claim).
func (Agent[I]) SpawnChild ¶
func (a Agent[I]) SpawnChild(ctx context.Context, sys Syscalls, input I, opts ...SpawnOption) (ProcessID, error)
SpawnChild launches a child Process from a strategy (journaled through sys, typed). The child insert is buffered into the transition commit (D48).
type AgentName ¶
type AgentName string
AgentName is the registry key of an agent definition (name + version + strategy). Process.Agent stores this wire value; a typo is detected at Spawn time as ErrUnknownAgent.
type AttemptInfo ¶
type AttemptInfo struct {
// Errors is the number of previous attempts that returned an error.
// Effects up to the error point may have fired.
Errors int
// UncleanReclaims is the number of previous claims that died
// mid-transition. Nothing is known about how far they got: the transition
// may have completed every effect and died before commit, and a
// lease-expiry reclaim may overlap a still-running predecessor.
UncleanReclaims int
}
AttemptInfo reports prior attempts at the current transition that did not commit. A zero value means this is the first attempt.
func (AttemptInfo) IsReplay ¶
func (a AttemptInfo) IsReplay() bool
IsReplay reports whether a previous attempt at this transition may have executed effects.
type Await ¶
type Await struct {
ProcessID ProcessID
Key AwaitKey
Kind AwaitKind
Status AwaitStatus
Deadline *time.Time
// Kind-specific fields (only the matching kind is non-zero). The kernel puts
// them on the row typed; row->bytes is the Repository implementation's job.
Question []byte // question: the Question() payload verbatim (encoding is the caller's).
Response []byte // response to a question (whatever Respond received; e.g. "yes"/"no").
Children []ProcessID // children: the children being waited on.
Results []ChildResult // children: the response (assembled by the kernel when all children finish).
Fired bool // timer: fired.
RespondedBy string
CreatedAt time.Time
RespondedAt *time.Time
}
Await is the persisted representation of a wait. Response acceptance upholds three invariants: only open is accepted, first-writer-wins, and the responder is recorded (optionally, via WithRespondedBy).
type AwaitKey ¶
type AwaitKey string
AwaitKey is a Process-local unique key for a "wait". Strategies name it.
type AwaitKind ¶
type AwaitKind string
AwaitKind is the kind of a "wait". Human confirmation (formerly approval) is NOT a distinct kind — a go/no-go confirmation is just a question.
type AwaitOption ¶
type AwaitOption func(*awaitConfig)
AwaitOption configures an AwaitSpec.
func WithDeadline ¶
func WithDeadline(t time.Time) AwaitOption
WithDeadline sets a Question deadline. Reaching it makes the await expired.
type AwaitSpec ¶
type AwaitSpec struct {
// contains filtered or unexported fields
}
AwaitSpec is a declared wait. It can only be built via the constructors below (three kinds only; confirmation is expressed as a question).
func Question ¶
func Question(key AwaitKey, payload []byte, opts ...AwaitOption) AwaitSpec
Question declares a wait for a human answer. Encoding of the payload is the caller's (a confirmation sends e.g. []byte("...question...") and the answer is []byte("yes"|"no")).
func WaitChildren ¶
WaitChildren declares a wait for all the given child Processes to finish.
type AwaitStatus ¶
type AwaitStatus string
AwaitStatus is the state of a wait.
const ( AwaitOpen AwaitStatus = "open" AwaitResponded AwaitStatus = "responded" AwaitExpired AwaitStatus = "expired" // deadline reached (question). AwaitCancelled AwaitStatus = "cancelled" // closed by the Process finishing/cancelling. )
type ChangeSet ¶
type ChangeSet struct {
Guards []ProcessGuard // write-free Process preconditions (read-set; for WaitChildren).
Processes []*Process // Rev-CAS upserts (write-set). May be several rows (child creation + parent wake).
Awaits []*Await // upserts keyed by (ProcessID, Key).
Events []*Event // appends (per-Process append order preserved).
}
ChangeSet is the unit of atomic persistence.
type ChildResult ¶
type ChildResult struct {
ProcessID ProcessID
Status ProcessStatus
Output []byte // the child's Output verbatim (format/type is known to the spawner).
Failure *Failure
// Metrics is the child's cumulative usage at the moment it finished, which
// already includes any children it folded in. The kernel adds these into the
// parent when the await resolves, so a Limit high in the tree sees what the
// tree below it spent rather than only the row it was called for.
Metrics Metrics
}
ChildResult is one child's outcome, delivered to the parent's children Await.
type ClaimHandler ¶
type ClaimHandler func(ctx context.Context, req *ClaimRequest) (ClaimOutcome, error)
ClaimHandler drives one claim to its end.
type ClaimMiddleware ¶
type ClaimMiddleware func(next ClaimHandler) ClaimHandler
ClaimMiddleware wraps a ClaimHandler.
The ctx it passes to next reaches the ToolFactory and every transition in the claim, which is what makes it the place to install a per-claim trace handler or logger. Three properties to design around:
It must call next AT MOST ONCE, for the same reason a StepMiddleware must: the first call settles the row, so a second would find it no longer running and achieve nothing. The second call returns ErrInvalidRequest.
It must call next SYNCHRONOUSLY and must not return before next has. This is not a convention: the claim holds a lease for as long as next runs, and how the row is settled is decided after the chain returns. A middleware that hands next to a goroutine and returns early — a timeout wrapper is the tempting shape — leaves the kernel asked to settle a row that is still being driven. The kernel does not do it: it logs, abandons the frame and reports ClaimAbandoned, letting the claim finish on its own. Bound a claim's work with WithMaxStepsPerClaim and the ctx, not by outrunning next.
Returning without calling next REFUSES the claim. The Process is put back to pending with a retry backoff and is claimed again once that elapses — it is a way to hold work off, not to drop it, and a middleware that refuses forever re-refuses on every backoff. The step attempt counter is not charged, since no Step ran.
The ClaimOutcome a middleware returns is what an outer middleware sees, but NOT what the scheduler acts on: the kernel reports what the claim actually did to the row. A middleware cannot make eager dispatch re-submit a Process by claiming the run was released.
A panic — here, or in anything the claim calls that is not already covered by the transition's own recovery — is turned into a claim failure and the Process is put back. It does not reach serveLoop, which has no recovery and would take the process down with the poll goroutine.
type ClaimOutcome ¶
type ClaimOutcome string
ClaimOutcome reports how one claim ended. It is not persisted anywhere; it exists so a ClaimMiddleware learns what became of the Process it wrapped.
const ( // ClaimRefused is the zero value: a middleware returned without calling next, // so the claim never ran. The kernel never reports it — a refused claim is put // back with a retry backoff and reported as ClaimRequeued, or as // ClaimAbandoned if the store would not take the write. It is what an OUTER // middleware sees returned from next. ClaimRefused ClaimOutcome = "" // ClaimFinished: the Process reached a terminal status during this claim. ClaimFinished ClaimOutcome = "finished" // ClaimSuspended: the Process committed as waiting. ClaimSuspended ClaimOutcome = "suspended" // ClaimRequeued: put back to pending with a backoff after a fault. The write // is confirmed — a requeue the store refused is ClaimAbandoned instead. ClaimRequeued ClaimOutcome = "requeued" // ClaimReleased: the step budget was spent, so the Process went back to // pending and is runnable now. This is the one outcome eager dispatch // re-submits on, and like ClaimRequeued it is confirmed rather than attempted. ClaimReleased ClaimOutcome = "released" // ClaimAbandoned: this worker walked away without moving the row, because the // lease was gone or a Repository call failed. A later claim recovers it — // through the lease expiring, which the next claim counts as an unclean // reclaim (ADR-0015), so a run of these is worth alerting on. ClaimAbandoned ClaimOutcome = "abandoned" )
type ClaimRequest ¶
type ClaimRequest struct {
// Process is a copy taken at claim time, so writing to it changes nothing that
// gets committed — the commit is built from the original. LeaseOwner names the
// worker and LeaseToken is this claim's fence identity, unique per claim.
Process *Process
}
ClaimRequest is one claim: a worker holding one lease on one Process, about to run a bounded run of transitions on it.
A claim is the outermost scope the kernel owns, and the only one that brackets the whole of a worker's work on a Process — ToolFactory construction, every transition in the run, and the write that settles the row. It is therefore where a per-claim resource is opened and closed, and where a ctx that must reach all of that is installed.
type Decision ¶
type Decision[O any] struct { // contains filtered or unexported fields }
Decision is the result of one transition, carrying the strategy's output type O. Its fields are unexported: it can only be built by Continue, Suspend, Fail or Done, so a Done without an output cannot be constructed.
Go infers a type argument from a call's arguments only, never from the return or assignment context. Done therefore infers O from its argument, while Continue/Suspend/Fail need it written out: agentkit.Continue[MyOut]().
func Done ¶
Done finalizes the Process as succeeded with the given output. The value is turned into the persisted bytes by the strategy's EncodeOutput, and the same value is handed to a completion handler without a round trip (ADR-0014).
func Fail ¶
func Fail[O any](code FailureCode, message string) Decision[O]
Fail finalizes the Process as failed.
func ResultDecision ¶
func ResultDecision[O any](res *StepResult) (Decision[O], bool)
ResultDecision reads the Decision as Decision[O]. ok == false means the result belongs to an agent whose output type is not O — for every kind, not only for a Done.
Unlike StepState and friends, O must be the agent's exact output type: Decision carries its own type witness, so ResultDecision[any] does NOT match an agent whose O is something else. To branch on what a transition decided without naming O, use DecisionKindOf.
func Suspend ¶
Suspend is a checkpoint that declares waits. specs are upserted on the transition commit. If an open wait already exists (e.g. declared in a prior transition), specs-less Suspend() is legal. If no open wait exists at commit time and no WaitChildren elision applies, the transition errors with ErrSuspendWithoutAwait. Re-declaring a non-open key is a no-op (idempotent re-execution after a restart).
type DecisionKind ¶
type DecisionKind string
DecisionKind is the outcome of one transition.
const ( DecisionContinue DecisionKind = "continue" DecisionSuspend DecisionKind = "suspend" DecisionDone DecisionKind = "done" DecisionFail DecisionKind = "fail" )
func DecisionKindOf ¶
func DecisionKindOf(res *StepResult) DecisionKind
DecisionKindOf reports what a result decided without naming O, for a middleware that only needs to branch on continue/suspend/done/fail.
type EffectContext ¶
type EffectContext struct {
ProcessID ProcessID
RootID ProcessID // correlation id for the whole tree (reaches down to children).
Agent AgentName
StateSeq int // transition number.
// Attempt reports prior attempts at this transition that did not commit, so
// a middleware can tell a replayed effect from a first one.
Attempt AttemptInfo
// Metadata is a COPY of Process.Metadata — the same kernel-opaque map
// WithMetadata set at spawn and a ToolFactory reads. It is here so a
// cross-cutting concern (per-tenant rate limiting, an audit field) can be
// written as middleware, which holds no Repository and could otherwise only
// get at it by reading the Process back per effect. Nil when the Process has
// none.
//
// It is the one field that is copied rather than shared: writing to it
// affects neither the Process nor any other effect. On InitRequest.Parent it
// is the PARENT's metadata — the child's is SpawnRequest.Metadata. Those two
// hold equal content whenever the spawner named no metadata of its own, since
// the child inherits (ADR-0011); they remain separate copies, so a
// SpawnMiddleware editing SpawnRequest.Metadata to strip a key changes what
// the child gets without touching what this field reports about the parent.
//
// Metadata is caller-supplied DATA, not a credential. A middleware branching
// on Metadata["tenant"] is trusting whoever called Spawn; that value must
// have been derived server-side from an already-validated principal before
// the spawn (ADR-0011).
Metadata map[string]string
// Limit is the Strategy's most recent Limit verdict, starting with the one
// taken at the transition boundary. Read it through Kind() and Message().
//
// A middleware wraps its own syscall's Limit check, so this carries the
// verdict from BEFORE this call: the transition boundary's for a
// transition's first effect, the previous effect's after that. That is the
// useful reading — "the budget looked like this going into this call".
//
// It does not change while the handler runs. This is a copy taken when the
// syscall started, so a middleware cannot see the post-effect re-evaluation
// by reading it again after calling next; a strategy reads that through
// Syscalls.LimitStatus().
//
// agentkit never acts on it. A middleware that wants a budget warning to
// reach the model appends Message() to GenerateRequest.SystemPrompt itself.
Limit LimitDecision
}
EffectContext identifies "which Process's which transition produced this call". It can key an audit row.
type Event ¶
type Event struct {
ID EventID // minted by the kernel; the Repository stores it verbatim.
ProcessID ProcessID
Type EventType
Key AwaitKey // target key for await.created (typed; the kernel builds no payload).
Payload []byte // the sys.Emit payload verbatim (nil for kernel-emitted events).
At time.Time
}
Event is an append-only record of an observable Process occurrence. Channel delivery (Slack, etc.) is done by the caller subscribing to these; this package only provides per-Process reads.
type EventID ¶
type EventID string
EventID identifies an Event. It is a uuid v7 (time-ordered) generated by the Kernel via uuid.NewV7(), like ProcessID. A Repository stores it verbatim and never mints one of its own — it is the handle a caller holds to resume a read (Kernel.ListEvents with WithAfterEvent).
type EventQuery ¶
type EventQuery struct {
// After is a cursor. "" starts from the first event; otherwise the result is
// the events appended strictly after the one with this ID. An ID this Process
// has no event for is ErrEventNotFound and returns no events — returning the
// whole list instead would reach the caller as a burst of new events, which
// it has no way to tell from the real thing.
After EventID
// Limit caps how many events are returned. <= 0 means no cap.
Limit int
}
EventQuery narrows a ListEvents read. Every field is optional, so the zero value means "all of this Process's events" — it is a struct rather than options so an implementation reads fields instead of resolving closures (ADR-0019).
type EventType ¶
type EventType string
EventType is the type of an observable Process event. Strategies may emit arbitrary types via sys.Emit (names that avoid the reserved three are recommended, not enforced).
type Failure ¶
type Failure struct {
Code FailureCode `json:"code"`
Message string `json:"message"`
}
Failure describes a failed Process.
type FailureCode ¶
type FailureCode string
FailureCode categorizes why a Process failed.
const ( // FailureStrategyError: Decision=Fail or an unrecoverable Step error. FailureStrategyError FailureCode = "strategy_error" // FailureLimitExceeded: stopped by the Strategy's Limit. FailureLimitExceeded FailureCode = "limit_exceeded" // FailureRetryExhausted: step retry limit exceeded. FailureRetryExhausted FailureCode = "retry_exhausted" // FailureUncleanReclaim: too many claims died mid-transition. This is a // worker-health signal rather than a strategy bug, which is why it is // distinct from FailureRetryExhausted — the cause and the remedy differ. FailureUncleanReclaim FailureCode = "unclean_reclaim" )
type FinishHandler ¶
FinishHandler runs after a Process reaches a terminal state and that state has been committed. Delivery is best-effort: it never fires twice, but a crash between the commit and the call loses it entirely (ADR-0014).
type FinishResult ¶
type FinishResult[O any] struct { // Status is the committed terminal status: ProcessSucceeded, ProcessFailed // or ProcessCancelled. Never a non-terminal status. Status ProcessStatus // Output is the value passed to Done. Non-nil if and only if // Status == ProcessSucceeded. Output *O // Failure is the recorded failure. Non-nil if and only if // Status == ProcessFailed. Failure *Failure }
FinishResult is the terminal outcome handed to a completion handler. Exactly one of Output / Failure is non-nil, or neither when cancelled.
type GenerateHandler ¶
type GenerateHandler func(ctx context.Context, req *GenerateRequest) (*GenerateResult, error)
GenerateHandler performs one LLM call.
type GenerateMiddleware ¶
type GenerateMiddleware func(next GenerateHandler) GenerateHandler
GenerateMiddleware wraps a GenerateHandler.
type GenerateOption ¶
type GenerateOption func(*GenerateRequest)
GenerateOption configures a Generate. Only input is required (D26). The options fill in a GenerateRequest, which is also what Generate middleware sees and may rewrite.
func WithHistory ¶
func WithHistory(h *gollem.History) GenerateOption
WithHistory passes prior conversation history (to gollem.WithSessionHistory).
func WithLLMOptions ¶
func WithLLMOptions(o ...gollem.GenerateOption) GenerateOption
WithLLMOptions passes gollem generate options (temperature, etc.) straight through. Note agentkit.GenerateOption and gollem.GenerateOption are distinct types (package-qualified); pass-through is via this option.
func WithLLMSessionOptions ¶
func WithLLMSessionOptions(o ...gollem.SessionOption) GenerateOption
WithLLMSessionOptions passes gollem session options straight through to LLMClient.NewSession. It reaches the session-scoped settings agentkit does not model as typed fields — gollem.WithSessionPromptCache and the content-block/stream middlewares — without agentkit growing a field per setting.
These are appended AFTER the options derived from Role/History/SystemPrompt/ Tools/Schema, and gollem applies session options in order, so the effect depends on which kind the option is: a scalar one (history, system prompt, content type, response schema, prompt cache) OVERRIDES what the typed field set, while a slice one (tools, content-block/stream middleware) ADDS to it. The order is fixed this way so a GenerateMiddleware can impose a setting on every agent; reversing it would let any strategy silently opt out.
The override reaches SessionGenerate's managed conversation too: passing gollem.WithSessionHistory here replaces the History the runtime is carrying. That is the same "extra options win" rule SessionGenerate already documents, not a separate hazard, but it is the one worth naming.
func WithRole ¶
func WithRole(r ModelRole) GenerateOption
WithRole selects the model role. Omitting it (or nil) means the default model.
func WithSchema ¶
func WithSchema(p *gollem.Parameter) GenerateOption
WithSchema requests JSON output against a schema (gollem WithSessionContentType(JSON) + WithSessionResponseSchema).
func WithSystemPrompt ¶
func WithSystemPrompt(p string) GenerateOption
WithSystemPrompt sets the system prompt (to gollem.WithSessionSystemPrompt).
func WithTools ¶
func WithTools(tools ...gollem.Tool) GenerateOption
WithTools declares tools to the LLM (execution goes through CallTool).
type GenerateRequest ¶
type GenerateRequest struct {
Effect EffectContext
Input []gollem.Input
Role ModelRole
History *gollem.History
SystemPrompt string
Tools []gollem.Tool
Schema *gollem.Parameter
LLMOptions []gollem.GenerateOption
// LLMSessionOptions is passed verbatim to LLMClient.NewSession, after the
// options derived from the typed fields above. It is the escape hatch for
// session-scoped settings agentkit does not model itself — prompt cache,
// content-block middleware — so a new gollem SessionOption needs no change
// here to be reachable.
//
// A GenerateMiddleware appending to it applies one session setting across
// every agent from a single Kernel registration. See WithLLMSessionOptions
// for what "after" means when an option collides with a typed field.
LLMSessionOptions []gollem.SessionOption
}
GenerateRequest carries everything one Generate needs. Every field is a concrete type, so a middleware assigns to them directly. Effect is filled in by the kernel and is not read back from here.
type GenerateResult ¶
type GenerateResult struct {
Texts []string `json:"texts"`
Thoughts []string `json:"thoughts,omitempty"`
FunctionCalls []*gollem.FunctionCall `json:"function_calls,omitempty"`
InputTokens int `json:"input_tokens"`
OutputTokens int `json:"output_tokens"`
CacheReadInputTokens int `json:"cache_read_input_tokens,omitempty"`
CacheCreationInputTokens int `json:"cache_creation_input_tokens,omitempty"`
History *gollem.History `json:"history"` // session history after the call (save it, pass it next time).
}
GenerateResult is the journalable/checkpointable result of a Generate. It is used instead of *gollem.Response because Response.Error is not JSON round-trippable; History is included so the strategy can fold it into its checkpointed state and pass it to the next Generate.
type HistoryRef ¶
type HistoryRef string
HistoryRef names one stored version of a Process's conversation History. It is opaque to the kernel: the store mints it in Save, and the kernel only records it on the Process record and hands it back to Load. The zero value means "no version has been committed yet", so a store must never return it.
type HistoryStore ¶
type HistoryStore interface {
// Save stores h as a NEW version and returns the ref naming it. It must not
// modify or replace any previously returned version. Returning the same ref
// for byte-identical content is allowed (a content-addressed implementation);
// returning a ref that later names DIFFERENT content is a contract violation,
// and it is the one that breaks the rollback guarantee. The returned ref must
// not be empty.
//
// A ref that sorts by creation time is preferred, so an operator can read the
// order of versions off the store, but it is not required.
Save(ctx context.Context, pid ProcessID, h *gollem.History) (HistoryRef, error)
// Load returns the version named by ref. The kernel never calls it with an
// empty ref. An unknown ref must be an error (ErrHistoryVersionMissing), not a
// nil result: a referenced version that is gone is data loss, not "nothing
// saved yet".
Load(ctx context.Context, pid ProcessID, ref HistoryRef) (*gollem.History, error)
// Discard reports that ref is no longer referenced. It is a NOTIFICATION, not
// a deletion order: reclaiming immediately, deferring to a sweep, or ignoring
// it are all conforming, and an unknown or already-discarded ref is not a
// failure. It returns nothing on purpose — the kernel would only log and carry
// on, so reporting a failed reclaim is the implementation's own business.
//
// Versions leak without it too: a crash between Save and the commit leaves one
// nobody will ever discard. A store is expected to have its own reclamation
// policy for those.
Discard(ctx context.Context, pid ProcessID, ref HistoryRef)
}
HistoryStore persists a Process's conversation History as immutable versions.
Which version is current is decided by Process.HistoryRef, which commits in the same Apply as State, so a save whose transition never committed is simply never referenced and the next attempt re-seeds from the committed one. That is what makes History roll back together with State, and it is why a stored version must never be rewritten in place (ADR-0017).
It is a port separate from Repository on purpose: History grows unbounded and does not belong in the transactional store, which carries only the ref. The implementation serializes *gollem.History; the kernel marshals nothing (ADR-0007).
type InheritedHistory ¶
type InheritedHistory struct {
Process ProcessID `json:"process"` // whose store key holds Ref.
Ref HistoryRef `json:"ref"`
}
InheritedHistory names a History version another Process committed, which a new Process starts its conversation from (WithInheritedHistory). Both fields are required: a HistoryStore addresses a version by (pid, ref), so the ref alone does not resolve.
It is resolved once, at Spawn, from the issuing Process's record, and never rewritten. The kernel only ever READS the version it names — the issuing Process's record may still name it too, so it is never Discarded (ADR-0017).
type InitHandler ¶
type InitHandler func(ctx context.Context, req *InitRequest) (*InitResult, error)
InitHandler builds the initial state for a Process.
type InitMiddleware ¶
type InitMiddleware func(next InitHandler) InitHandler
InitMiddleware wraps an InitHandler.
type InitRequest ¶
type InitRequest struct {
ProcessID ProcessID
Agent AgentName
Parent *EffectContext
// contains filtered or unexported fields
}
InitRequest is one call to a strategy's Init. ProcessID is already minted when Init runs, so an audit record can be correlated with the Process that the call is about to create.
An Init middleware fires for both entry points: Agent[I].Spawn (Parent is nil) and Agent[I].SpawnChild (Parent is the spawning transition). It also fires on an idempotent Spawn that ends up returning an existing Process — the initial state is built before the idempotency key is looked up — and in that case the ProcessID here is discarded.
func NewInitRequest ¶
func NewInitRequest[I any](req *InitRequest, input I) *InitRequest
NewInitRequest returns a shallow copy of req with the input replaced. req is left untouched, so an outer middleware never sees its own request change.
I is not constrained to the agent's input type — a kernel middleware spans every agent and cannot know it. Passing the wrong type compiles and surfaces as ErrInvalidRequest when the binding runs. Reading with InitInput first and writing the value back is the form that keeps the types aligned.
type InitResult ¶
type InitResult struct {
// contains filtered or unexported fields
}
InitResult is the initial strategy state an Init call produced.
func NewInitResult ¶
func NewInitResult[S any](state S) *InitResult
NewInitResult builds a result without calling next, i.e. it replaces the strategy's own Init. As with NewInitRequest, S is unchecked here and a mismatch surfaces as ErrInvalidRequest when the state is encoded.
type Kernel ¶
type Kernel struct {
// contains filtered or unexported fields
}
Kernel is the Process lifecycle API and worker loop (the proposal's Engine + Worker merged into one type, D5). All held state is immutable injected references — no cross-request state in process memory.
func New ¶
func New(repo Repository, model gollem.LLMClient, agents *Registry, opts ...KernelOption) (*Kernel, error)
New constructs a Kernel. model is the default model (what a nil ModelRole resolves to); an agent runtime needs at least one model, so it is positional (D26/D27). Static validation (ErrInvalidConfig): repo / model / agents non-nil.
func (*Kernel) Cancel ¶
Cancel requests cancellation. pending/waiting -> immediately cancelled (open awaits cancelled, process.finished event). running -> only sets cancel_requested (the worker finalizes at the next transition boundary). terminal -> ErrProcessFinished.
func (*Kernel) GetProcess ¶
GetProcess returns the Process (read-through to the Repository).
func (*Kernel) ListAwaits ¶
ListAwaits returns the Process's awaits.
func (*Kernel) ListEvents ¶
func (k *Kernel) ListEvents(ctx context.Context, pid ProcessID, opts ...ListEventsOption) ([]*Event, error)
ListEvents returns the Process's events in append order. With no options it returns all of them; see WithAfterEvent and WithEventLimit to read incrementally.
func (*Kernel) Respond ¶
func (k *Kernel) Respond(ctx context.Context, pid ProcessID, key AwaitKey, response []byte, opts ...RespondOption) error
Respond delivers a response to a question await (a confirmation's yes/no is sent this way too). Responding to a non-question await is ErrInvalidRequest. Only open is accepted (ErrAwaitClosed); first-writer-wins. The kernel only nil-checks the payload.
func (*Kernel) Serve ¶
func (k *Kernel) Serve(ctx context.Context, opts ...ServeOption) error
Serve runs claim loops until ctx is done (blocking). WithPollConcurrency loops share a workerID; the per-claim LeaseToken is the fence identity (D50). It also installs the eager dispatcher for this Kernel: a Process becoming runnable here (Spawn/Respond/child/parent) is driven immediately rather than at the next poll (ADR-0016). Only one Serve may be active per Kernel; a second returns ErrServeActive, because the dispatcher and its concurrency semaphore are per-Serve state that a second Serve would silently clobber.
type KernelOption ¶
type KernelOption func(*kernelConfig)
KernelOption configures a Kernel.
func WithClaimMiddleware ¶
func WithClaimMiddleware(mw ...ClaimMiddleware) KernelOption
WithClaimMiddleware adds Claim middleware. Repeatable; the first registered is the outermost. A nil element makes New return ErrInvalidConfig.
func WithClock ¶
func WithClock(fn func() time.Time) KernelOption
WithClock sets the clock that Now() returns (a test seam). Default: time.Now. Determinism is not provided (D44).
func WithGenerateMiddleware ¶
func WithGenerateMiddleware(mw ...GenerateMiddleware) KernelOption
WithGenerateMiddleware adds Generate middleware (same ordering and nil rules).
func WithInitMiddleware ¶
func WithInitMiddleware(mw ...InitMiddleware) KernelOption
WithInitMiddleware adds Init middleware. Repeatable; the first registered is the outermost. A nil element makes New return ErrInvalidConfig.
func WithLogger ¶
func WithLogger(l *slog.Logger) KernelOption
WithLogger sets the logger. Default: slog.Default().
func WithModelRole ¶
func WithModelRole(role ModelRole, client gollem.LLMClient) KernelOption
WithModelRole assigns a client to a role (repeatable). A nil role is ErrInvalidConfig (the default is passed positionally to New).
func WithSpawnMiddleware ¶
func WithSpawnMiddleware(mw ...SpawnMiddleware) KernelOption
WithSpawnMiddleware adds SpawnChild middleware (same ordering and nil rules).
func WithStepMiddleware ¶
func WithStepMiddleware(mw ...StepMiddleware) KernelOption
WithStepMiddleware adds Step middleware (same ordering and nil rules).
func WithToolCallMiddleware ¶
func WithToolCallMiddleware(mw ...ToolCallMiddleware) KernelOption
WithToolCallMiddleware adds CallTool middleware (same ordering and nil rules).
func WithToolFactory ¶
func WithToolFactory(f ToolFactory) KernelOption
WithToolFactory sets the tool factory. Default: none (no tools).
type LimitDecision ¶
type LimitDecision struct {
// contains filtered or unexported fields
}
LimitDecision is a Limit verdict: one kind, plus the message that goes with it. Build it with LimitPass, LimitNotice or LimitStop.
It is both what Strategy.Limit returns and what a strategy observes through Syscalls.LimitStatus(), so a field added here reaches readers without changing any signature. The message is one field rather than a separate reason and notice because a decision is exactly one kind: "stopped, and also here is an unrelated notice" is not a state that exists.
The kernel never interprets the message. It moves it to whoever asked for it (ADR-0011).
func LimitNotice ¶
func LimitNotice(msg string) LimitDecision
LimitNotice continues but attaches a message for the strategy, which may read it through Syscalls.LimitStatus() and act on it — put it in a prompt, drop expensive tools, wrap up early. agentkit itself does none of that: a decision nobody reads costs nothing beyond the Limit call.
An empty message is a LimitPass, so a LimitKindNotice always carries text and a reader never has to test for both.
func LimitStop ¶
func LimitStop(reason string) LimitDecision
LimitStop refuses to continue. Before an effect the reason reaches the strategy wrapped in ErrLimitExceeded; at a transition boundary it becomes the Failure message of a failed(limit_exceeded) Process.
An empty reason is replaced, because Failure.Message must say something.
func (LimitDecision) Kind ¶
func (d LimitDecision) Kind() LimitKind
Kind reports which verdict this is. The zero LimitDecision reads as LimitKindPass, so a reader holding one taken before any verdict was recorded needs no special case.
func (LimitDecision) Message ¶
func (d LimitDecision) Message() string
Message is the text Limit attached: the notice for LimitKindNotice, the reason for LimitKindStop, and "" for LimitKindPass.
type LimitKind ¶
type LimitKind string
LimitKind is which of the three verdicts a LimitDecision carries.
The constants carry the full type name as their prefix, unlike AwaitKind's AwaitQuestion or DecisionKind's DecisionContinue. Those get away with the short form because their constructors are named differently (Question, Continue); here LimitPass / LimitNotice / LimitStop are the constructors.
type Limiter ¶
type Limiter func(ctx context.Context, proc *Process, metrics Metrics) LimitDecision
Limiter decides whether a Process may continue. Measurement (Metrics) is the Kernel's job; the decision is the strategy's, expressed as Strategy.Limit, whose shape this type is (ADR-0010). It is also the argument type the bundled strategies take to build that method from a caller's closure.
It runs at three points: at each transition boundary, before every Generate, CallTool and SpawnChild, and again after each of those has been metered. The first two refuse the work when the verdict says stop; the third cannot — the effect has run — and only updates what Syscalls.LimitStatus() reports.
metrics is a snapshot of "committed cumulative (proc.Metrics) plus what this run has accumulated so far", so an effect cannot consume budget without the next call seeing it.
Two obligations follow from being called that often, roughly 1 + 2×effects times per attempt:
- It must be READ-ONLY with respect to whatever it consults. A call is an enquiry, not an acquisition: the same effect is asked about more than once, and once with the work already done. A Limiter that draws a token from a rate limiter, or charges a quota, charges several times per effect and refuses work nobody performed. Consult the current state instead and leave the accounting to whatever owns it.
- It must be cheap and NON-BLOCKING. It is on the transition hot path and the claim holds its lease throughout, so waiting here turns a throttle into a lease expiry and an unclean reclaim (ADR-0010, ADR-0015). Work that has to wait belongs behind a timer await.
type ListEventsOption ¶
type ListEventsOption func(*EventQuery)
ListEventsOption configures a ListEvents read. It resolves into the EventQuery the Repository receives: options at the entry point (ADR-0005), plain fields at the SPI.
func WithAfterEvent ¶
func WithAfterEvent(id EventID) ListEventsOption
WithAfterEvent returns only the events appended after id, so a caller that stored the last id it saw resumes there instead of re-reading the whole list. An id this Process has no event for is ErrEventNotFound rather than a silent restart from the beginning.
func WithEventLimit ¶
func WithEventLimit(n int) ListEventsOption
WithEventLimit caps how many events are returned. n <= 0 means no cap.
type Metrics ¶
type Metrics struct {
InputTokens int64 `json:"input_tokens,omitempty"`
OutputTokens int64 `json:"output_tokens,omitempty"`
// CacheReadInputTokens and CacheCreationInputTokens are COMPONENTS OF
// InputTokens, not additions to it — the same relation gollem.Response
// defines, where InputToken is the true total of all three:
// InputTokens = uncached input + CacheCreationInputTokens + CacheReadInputTokens.
//
// InputTokens - CacheReadInputTokens is the input NOT served from cache —
// uncached input plus any cache write, not a single price tier: a cache
// write is commonly billed at a premium over uncached input, and a cache
// read at a discount. Pricing is the caller's to know (model, contract,
// date); agentkit only carries the three counts, not a rate.
//
// Only Claude reports cache writes; the field is 0 for providers that do
// not, which is indistinguishable from "caching was not used" and is
// intentionally not corrected here.
CacheReadInputTokens int64 `json:"cache_read_input_tokens,omitempty"`
CacheCreationInputTokens int64 `json:"cache_creation_input_tokens,omitempty"`
LLMCalls int64 `json:"llm_calls,omitempty"`
ToolCalls int64 `json:"tool_calls,omitempty"`
Steps int64 `json:"steps,omitempty"`
Spawns int64 `json:"spawns,omitempty"`
}
Metrics is the fixed set of counters the Kernel maintains. The set is closed (ADR-0010): a caller cannot add one, which is why this is a struct rather than a map — a map would advertise a key space that does not exist.
Every field is cumulative and never decreases. The zero value means "nothing consumed" and is a valid Metrics.
Process.Metrics counts a Process's own effects plus every child that has terminated, once each, so a Limit high in a tree sees what the subtree spent rather than only the row it was called for.
The json tags match the keys the previous map form produced, so a snapshot written before this became a struct still reads back: known counters keep their values, and a null (a nil map) becomes the zero value. No migration is needed for that.
The wire form is not byte-identical, though. Zero metrics used to marshal as null and now marshal as {}, and a key outside this set — which the old map type could hold, even though the kernel never wrote one — is dropped on read and gone on the next write.
type ModelRole ¶
type ModelRole interface {
// String returns the diagnostic display name.
String() string
// contains filtered or unexported methods
}
ModelRole denotes the intended use of a model (e.g. "planner"). It is a sealed interface: the unexported marker method modelRole() makes it unimplementable outside this package, so only values returned by DefineModelRole can exist. Resolution is by pointer identity — two Define calls with the same name are NOT equal. A nil ModelRole means "the default model" (the required argument of New). name is for diagnostics only.
func DefineModelRole ¶
DefineModelRole returns a fresh ModelRole. Callers keep the result in a package variable and share that value; every call returns a new identity.
type Process ¶
type Process struct {
ID ProcessID
Agent AgentName
Status ProcessStatus
Metadata map[string]string // optional Spawn WithMetadata. Infrastructure-facing
// process scope (e.g. "tenant"->"acme") for ToolFactory. The kernel does not
// interpret it, and it is NOT strategy input. It is not a credential
// (WithMetadata callers must derive it server-side from a validated principal).
Output []byte // non-nil when succeeded. Consumed by the parent's children Await and GetProcess.
Failure *Failure // non-nil when failed.
State []byte // EncodeState output, stored verbatim (the kernel never converts it).
StateVersion int // strategy version that wrote State (the first arg to DecodeState).
StateSeq int // number of committed transitions. 0 = first Step not yet committed.
// HistoryRef names the committed version of this Process's conversation
// History in its HistoryStore, or "" when none has been committed. The worker
// saves a NEW version before the commit and records its ref here inside the
// same Apply, so History rolls back together with State (ADR-0017).
HistoryRef HistoryRef
// InheritedHistory names the version another Process committed that this one
// starts its conversation from, or nil. Set once at Spawn and never written
// again: this Process saves its own versions under its OWN id, and HistoryRef
// takes precedence as soon as it commits one. What is left behind is a record
// of where the conversation came from. The kernel never Discards the version
// named here (ADR-0017).
InheritedHistory *InheritedHistory
StepAttempts int // failure count of the current transition (reset to 0 on a successful commit).
// UncleanReclaims counts claims that took over this Process after its
// previous claim died mid-transition. Same reset scope as StepAttempts.
// Maintained by ClaimNextProcess (see the Repository contract), never by the
// worker — the worker only reads it to bound re-execution. Eager dispatch
// (claimSpecific) claims only pending rows, so it never increments this;
// reclaiming an expired-lease running row is ClaimNextProcess's job alone.
UncleanReclaims int
Metrics Metrics // committed cumulative usage.
ParentID *ProcessID
RootID ProcessID // self if no parent.
Subject *SubjectRef
IdempotencyKey string
CancelRequested bool
CancelReason string
WakeAt *time.Time // wake time while waiting (min of open await deadlines).
LeaseOwner string // diagnostic (hostname/uuid). Not used for fencing (shared across WithPollConcurrency claims).
LeaseToken string // unique per claim (ClaimNextProcess and eager claimSpecific each mint a uuid v7). The fence identity: a worker
// keeps its claim's LeaseToken and, on conflict, checks "stored LeaseToken == mine" to tell "still hold the
// lease (rebase ok)" from "re-claimed by another worker (abandon)".
LeaseUntil *time.Time
Rev int64 // optimistic concurrency token. Incremented on every Apply/claim write. Also used to detect lease expiry.
CreatedAt time.Time
UpdatedAt time.Time
}
Process is the complete persisted representation of an execution unit. It is the aggregate the Repository stores.
type ProcessGuard ¶
ProcessGuard is a write-free precondition on a Process row (a read-set Rev CAS). Its main use is making WaitChildren check-then-act atomic (the parent reads child states to decide elision, then guards on the children's Revs).
type ProcessID ¶
type ProcessID string
ProcessID identifies a Process. It is a uuid v7 (time-ordered) generated by the Kernel via uuid.NewV7().
type ProcessStatus ¶
type ProcessStatus string
ProcessStatus is the lifecycle state of a Process. The proposal's limit_exceeded status is dropped; it is expressed as failed + FailureCode.
const ( ProcessPending ProcessStatus = "pending" ProcessRunning ProcessStatus = "running" ProcessWaiting ProcessStatus = "waiting" ProcessSucceeded ProcessStatus = "succeeded" ProcessFailed ProcessStatus = "failed" ProcessCancelled ProcessStatus = "cancelled" )
func (ProcessStatus) Terminal ¶
func (s ProcessStatus) Terminal() bool
Terminal reports whether the status is a final state (no further transitions).
type RegisterOption ¶
type RegisterOption[O any] func(*registerConfig[O])
RegisterOption configures Register.
func WithHistoryStore ¶
func WithHistoryStore[O any](hs HistoryStore) RegisterOption[O]
WithHistoryStore opts this agent into runtime-managed conversation History persistence, enabling sys.Session(). When set, the worker lazily loads the version named by Process.HistoryRef on first use, and before each transition commit — including terminal commits, so a later restart/handoff can read the final transcript — saves a new version and records its ref in the same Apply (ADR-0017). Without it, the Session methods return ErrHistoryNotConfigured (the managed conversation is not silently run without persistence); a strategy that manages History itself uses the primitive Generate instead. The store is a SEPARATE port (blob storage) from the Kernel's Repository, injected here per agent rather than on the Kernel.
func WithOnFinish ¶
func WithOnFinish[O any](h FinishHandler[O]) RegisterOption[O]
WithOnFinish wires a completion handler for this agent. The handler runs synchronously on whichever instance committed the terminal transition, after the commit succeeded; its error and panic are logged and change nothing. A nil handler yields ErrInvalidAgentDef.
type Registry ¶
type Registry struct {
// contains filtered or unexported fields
}
Registry maps agent name -> type-erased binding. Register completes before Spawn/Serve start; thereafter the Registry is read-only.
type Repository ¶
type Repository interface {
// GetProcess returns the Process. Absent -> ErrProcessNotFound.
GetProcess(ctx context.Context, pid ProcessID) (*Process, error)
// FindProcessByIdempotencyKey finds a Process by its idempotency key. Absent -> ErrProcessNotFound.
FindProcessByIdempotencyKey(ctx context.Context, key string) (*Process, error)
// FindOpenProcessBySubject finds an open (pending/running/waiting) Process holding subject. Absent -> ErrProcessNotFound.
FindOpenProcessBySubject(ctx context.Context, subject SubjectRef) (*Process, error)
// ClaimNextProcess atomically claims one runnable Process. Targets:
// status=pending with wake_at unset or <=now, or status=waiting with
// wake_at<=now, or status=running with lease_until<now (lease expired). No
// target -> (nil, nil) (not an error). A claim from status=running also
// increments unclean_reclaims (contract 4).
//
// The two wake_at conditions differ on purpose: a pending row without one is
// runnable now, whereas a waiting row without one is waiting for a response
// and must never wake by itself.
ClaimNextProcess(ctx context.Context, workerID string, leaseUntil time.Time, now time.Time) (*Process, error)
// ListAwaits returns all awaits of a Process.
ListAwaits(ctx context.Context, pid ProcessID) ([]*Await, error)
// ListEvents returns a Process's events in append order, narrowed by q. A
// zero EventQuery returns all of them.
ListEvents(ctx context.Context, pid ProcessID, q EventQuery) ([]*Event, error)
// Apply applies a ChangeSet atomically (see the contract above). On any
// precondition failure it writes nothing and returns ErrConflict.
Apply(ctx context.Context, cs ChangeSet) error
}
Repository is the kernel's persistence contract. It is an SPI implemented and injected by the caller; the application never calls it directly (reads go through Kernel.GetProcess / ListAwaits / ListEvents). It requires no transaction mechanism — only atomic application of a change set and conditional writes (Rev-based optimistic concurrency). The realization (RDB TX / Firestore TX / conditional write / mutex) is the implementation's choice.
Contract (also verified by repository/repotest):
- Apply applies the whole ChangeSet atomically — all or nothing.
- Apply checks each Processes row's stored Rev against the row's Rev (CAS); a single mismatch writes nothing and returns ErrConflict. On success each row's Rev is +1'd (a new insert of a Rev=0 row is stored as Rev=1). This fences stale-worker commits.
- Apply checks each Guards ProcessGuard's Rev (a read-only precondition; no write, so Rev is not advanced) — used for WaitChildren check-then-act.
- ClaimNextProcess never double-claims a Process across concurrent workers (atomic claim, +1 Rev). It writes status=running / lease_owner=workerID / lease_token=new-uuid-v7 / lease_until, and mints a fresh lease_token every claim (even a re-claim by the same workerID) — the fence identity. When the target was status=running (an expired or absent lease) — i.e. the previous claim died mid-transition — it also increments unclean_reclaims. A claim from pending or waiting leaves it unchanged, and ClaimNextProcess never writes step_attempts. This is what bounds re-execution after a crash; an implementation that skips it degrades to unbounded replay. A pending row whose wake_at is still in the future is NOT a target: that is the retry backoff the worker writes when it puts a failed transition back. An implementation that claims it anyway still runs correctly, but retries as fast as it polls. ClaimNextProcess and Apply are mutually linearizable on the same Process row: a claim and a Rev-CAS Apply that both read the row at Rev N cannot both succeed — exactly one advances it to N+1 and the other observes the new Rev (a claim finds nothing to claim; an Apply returns ErrConflict). This is what lets eager dispatch claim a specific pending row via Apply without a dedicated SPI method, racing a poller's ClaimNextProcess safely.
- Uniqueness is maintained: idempotency_key / open Process subject / (process_id, await_key). An insert violation writes nothing and returns ErrConflict.
- ListEvents preserves per-Process append order, and round-trips each Event's kernel-assigned ID verbatim. An implementation never mints or rewrites one: the ID is what a caller holds as a cursor, so a value that changed between write and read would resume from the wrong place.
type RespondOption ¶
type RespondOption func(*respondConfig)
RespondOption configures a Respond.
func WithRespondedBy ¶
func WithRespondedBy(id string) RespondOption
WithRespondedBy records the responder (Await.RespondedBy) for audit. Optional.
type RetryBackoff ¶
RetryBackoff decides how long a requeued Process waits before it is claimable again. attempts is the error count the requeue is about to store, so the first failure of a transition asks for attempts=1.
A fault that is not the strategy's — a ToolFactory error, a refused or failing Claim middleware — does not charge an attempt, so it asks with the count unchanged (0 unless an earlier transition already failed). A middleware that refuses every claim therefore keeps asking with the same number: return a constant there rather than expecting the curve to climb.
It runs on the requeue path while the claim still holds its lease. Keep it pure and cheap — do not block, and do not reach for a store.
type ServeOption ¶
type ServeOption func(*serveConfig)
ServeOption configures Serve.
func WithLease ¶
func WithLease(d time.Duration) ServeOption
WithLease sets the lease duration. Default: 60s.
func WithMaxCancelDeferrals ¶ added in v0.3.0
func WithMaxCancelDeferrals(n int) ServeOption
WithMaxCancelDeferrals bounds how many transitions a claim may run after it observes a cancel request while the managed conversation still holds a tool call nobody answered. Default: one less than WithMaxStepsPerClaim, i.e. the rest of the claim. Zero finalizes at the first boundary, which is the behaviour from before this option existed. It is clamped below WithMaxStepsPerClaim so a cancel always lands inside the claim that observed it rather than surviving a release and starting the count over.
Raising it does not make a cancel land on a usable transcript by itself: only a strategy that answers its tool calls produces a boundary worth waiting for. See docs/writing-strategies.md.
func WithMaxConcurrent ¶
func WithMaxConcurrent(n int) ServeOption
WithMaxConcurrent sets the hard limit: the maximum number of claims this Serve drives at once, counting both poll loops and eager dispatch. Default: 64. It is the capacity of a single semaphore shared by both; eager dispatch may burst up to it, and WithPollConcurrency is clamped to it. This bounds concurrent drivers, not `running` rows (a driver that panics frees its slot while the row stays running until its lease expires; other instances are not counted).
func WithMaxStepAttempts ¶
func WithMaxStepAttempts(n int) ServeOption
WithMaxStepAttempts sets the step retry limit. Default: 3. This bounds attempts that ended in an ERROR; a claim that died mid-transition is bounded separately by WithMaxUncleanReclaims.
func WithMaxStepsPerClaim ¶
func WithMaxStepsPerClaim(n int) ServeOption
WithMaxStepsPerClaim sets how many transitions one claim runs. Default: 16. A value < 1 is treated as the default (0 would run no transition and release, which under eager dispatch re-submits in a tight loop).
func WithMaxUncleanReclaims ¶
func WithMaxUncleanReclaims(n int) ServeOption
WithMaxUncleanReclaims bounds how many times a Process may be taken over after a claim died mid-transition. Default: 3. Exceeding it terminates the Process as failed with FailureUncleanReclaim.
This is deliberately separate from WithMaxStepAttempts: an error tells the strategy how far the previous attempt got, whereas a vanished claim tells it nothing — the transition may have run every effect and died before its commit, and a lease-expiry reclaim may overlap a predecessor that is still running. Callers that cannot tolerate duplicated side effects set this to 0.
The bound is compared as `UncleanReclaims > n`, matching the `StepAttempts+1 > n` convention: n permits n further attempts after the first. n=0 therefore finalizes the Process on the first unclean reclaim, without running Step at all.
func WithPollConcurrency ¶
func WithPollConcurrency(n int) ServeOption
WithPollConcurrency sets the number of parallel poll (claim) loops — the soft limit on polling-driven concurrency. Default: 1. It is sub-capped by the hard limit (WithMaxConcurrent).
func WithPollInterval ¶
func WithPollInterval(d time.Duration) ServeOption
WithPollInterval sets the claim poll interval. Default: 500ms.
func WithRetryBackoff ¶
func WithRetryBackoff(fn RetryBackoff) ServeOption
WithRetryBackoff sets the wait a requeued Process serves before it becomes claimable again. Default: 2^attempts seconds, capped at a minute. A nil function restores that default, and a negative duration is treated as zero.
The kernel decides *whether* to retry (WithMaxStepAttempts) and you decide *how soon*. Two things the fixed default cannot express are the usual reasons to set it: jitter, so a fleet that failed together does not retry together; and a shorter curve in tests, which otherwise spend the real seconds.
agentkit.WithRetryBackoff(func(attempts int) time.Duration {
base := min(time.Duration(1<<min(attempts, 6))*time.Second, time.Minute)
return base + time.Duration(rand.Int64N(int64(base/4)))
})
The wait is written to the Process as its wake time, and a pending Process is not claimable until it passes. A Repository that does not honour that (see the ClaimNextProcess contract) will retry as fast as it polls whatever this says.
func WithWorkerID ¶
func WithWorkerID(id string) ServeOption
WithWorkerID sets the worker id (diagnostic). Default: hostname + "/" + uuid v7.
type Session ¶
type Session interface {
// Generate runs one LLM turn in the managed conversation. Tools are bound
// from Syscalls.Tools() (gollem fixes tools at session construction, and a
// stable tool set is also what prompt caching wants). Extra GenerateOption
// values are applied after the injected History/Tools and so can override
// them.
//
// Passing no input at all is meaningful: it continues from the History as it
// stands, which is what follows a CallTool that already appended its result.
Generate(ctx context.Context, input []gollem.Input, opts ...GenerateOption) (*GenerateResult, error)
// CallTool runs a tool call like Syscalls.CallTool and appends its result to
// the conversation, so the History a Step boundary commits can end on a closed
// tool_use/tool_result pair without spending another LLM turn.
//
// Use it for a call the MODEL asked for. For a call the strategy makes on its
// own initiative, use Syscalls.CallTool: appending a tool_response with no
// matching tool_use would corrupt the conversation, and the kernel cannot tell
// the two apart.
//
// Exactly one tool_response is appended per call, whatever the outcome. On an
// error (unknown tool, invalid arguments, a failing Run, a middleware that
// refused, or a result the conversation format cannot encode) the appended
// result carries IsError with the error text, and the error is ALSO returned.
// Leaving the pair open is not an option — the model asked for this call and
// the next request has to answer it.
//
// Results that answer the SAME model turn are appended to one tool message,
// not one message each: a provider counts tool results per turn, so calling
// this once per call of a parallel tool round still produces a conversation
// the next request can send.
//
// It needs the conversation to already hold a History (a Generate earlier in
// this transition, or a committed one), because a History carries the provider
// identity and the kernel cannot invent it; otherwise ErrInvalidRequest.
CallTool(ctx context.Context, call gollem.FunctionCall) (map[string]any, error)
// History returns the conversation's current history: the working copy once
// this transition has advanced it, otherwise the committed version (loaded on
// first use, so it reflects stored History even before the first Generate of a
// fresh claim).
History(ctx context.Context) (*gollem.History, error)
// contains filtered or unexported methods
}
Session is the Process's managed conversation: the runtime carries History across calls and, once committed, across steps and workers, so a strategy threads neither History nor tools by hand. History is persisted by the worker before the next commit (ADR-0017).
It is scoped to one transition, like the Syscalls it came from. Do not keep it in strategy state.
Every method requires the agent to have been registered with WithHistoryStore and returns ErrHistoryNotConfigured otherwise, so the managed conversation is never silently run without persistence. For a strategy that manages History itself, the primitive Syscalls.Generate with WithHistory is still there.
type SpawnHandler ¶
type SpawnHandler func(ctx context.Context, req *SpawnRequest) (ProcessID, error)
SpawnHandler buffers one child Process into the transition commit and returns its freshly minted id. The child is not persisted until the transition commits.
type SpawnMiddleware ¶
type SpawnMiddleware func(next SpawnHandler) SpawnHandler
SpawnMiddleware wraps a SpawnHandler.
type SpawnOption ¶
type SpawnOption func(*spawnConfig)
SpawnOption configures a Spawn / SpawnChild.
func WithIdempotencyKey ¶
func WithIdempotencyKey(key string) SpawnOption
WithIdempotencyKey makes Spawn return the existing Process's ID if one already matches (not an error). Not usable on SpawnChild (child creation is buffered into the transition commit and has no dedup); specifying it there is ErrInvalidRequest.
func WithInheritedHistory ¶
func WithInheritedHistory(from ProcessID) SpawnOption
WithInheritedHistory starts the new Process's conversation from the version `from` has committed, instead of from an empty one. Use it to continue a finished Process's conversation in a Process of its own — one whose Metrics, limits and cancellation are its own (ADR-0017).
The version is resolved from `from`'s record at Spawn and pinned on the new Process, so a later turn of `from` does not change what this one starts from. The new Process saves its own versions under its own id, and the kernel never Discards the inherited one.
`from` must exist and must have committed a conversation, or Spawn fails with ErrProcessNotFound / ErrInvalidRequest; the agent must be registered with WithHistoryStore, or ErrHistoryNotConfigured. What the kernel does NOT promise is that the inherited version survives: if `from` is still running, its next commit releases the version this Process was pointed at (Discard is a notification, so whether it is really reclaimed is the store's call). Inherit from a finished Process, or accept that.
Not usable on SpawnChild: a strategy has no way to obtain a version to inherit (Syscalls hands out no HistoryRef), so the option would only be reachable with a ref smuggled in from outside. Specifying it there is ErrInvalidRequest.
func WithMetadata ¶
func WithMetadata(m map[string]string) SpawnOption
WithMetadata sets Process.Metadata (infrastructure-facing scope for ToolFactory; not a credential — derive it server-side from a validated principal).
On SpawnChild it REPLACES the parent's map rather than merging into it, and omitting it inherits the parent's. Merging would leave a caller no way to drop a key the parent carries; replacing makes both outcomes reachable, with an empty map meaning "this child gets none".
func WithSubject ¶
func WithSubject(ref SubjectRef) SpawnOption
WithSubject sets a turn-lock subject. An open Process holding the same subject makes Spawn return ErrSubjectBusy.
type SpawnRequest ¶
type SpawnRequest struct {
Effect EffectContext
Agent AgentName
Metadata map[string]string
Subject *SubjectRef
// OnCommit registers fn to be called exactly once with this TRANSITION's
// commit outcome: nil when the transition committed, non-nil when it did
// not.
//
// The scope is the transition, not the child, and the distinction is not
// pedantic: registering before calling next means fn still fires with nil if
// next failed but the transition went on to commit anyway — no child exists
// in that case. Register after a successful next to bind fn to a child that
// was really buffered.
//
// fn runs after the commit, outside the transition, so a panic in it cannot
// become a transition error; it is recovered and logged. A nil fn is
// ignored.
OnCommit func(fn func(err error))
// contains filtered or unexported fields
}
SpawnRequest is one SpawnChild. The launch options are resolved into fields so a middleware can read them; WithIdempotencyKey is rejected before the chain runs because it is never valid on a child.
func NewSpawnRequest ¶
func NewSpawnRequest[I any](req *SpawnRequest, input I) *SpawnRequest
NewSpawnRequest returns a shallow copy of req with the input replaced. req is left untouched. I is unchecked here (see NewInitRequest); a mismatch surfaces as ErrInvalidRequest when the child's Init runs.
type StepHandler ¶
type StepHandler func(ctx context.Context, req *StepRequest) (*StepResult, error)
StepHandler runs one transition of a strategy.
type StepMiddleware ¶
type StepMiddleware func(next StepHandler) StepHandler
StepMiddleware wraps a StepHandler.
Unlike the effect middleware, it must call next AT MOST ONCE. A Step's side effects — spawned children, emitted events, metrics — accumulate per transition rather than per call, so a second attempt would commit the first attempt's effects together with its own state and Decision. The second call returns ErrInvalidRequest rather than doing that quietly.
To decide a transition without running the strategy, do not call next at all and return a StepResult built with NewStepResult.
type StepRequest ¶
type StepRequest struct {
Effect EffectContext
// Process is a copy taken for this transition, so writing to it changes
// nothing that gets committed. It is a snapshot for deciding and recording,
// not a way to edit the row.
Process *Process
// Sys is the syscall surface handed to Step. It may be replaced; Syscalls is
// sealed against outside implementations but can be wrapped by embedding it
// (type counting struct{ agentkit.Syscalls }) and overriding what you need.
Sys Syscalls
// contains filtered or unexported fields
}
StepRequest is one call to a strategy's Step, between DecodeState and EncodeState.
What a Step middleware observes is the Step CALL, not the transition's commit: the commit happens after the handler returns, outside this chain. A transition that fails to commit is re-run from the last checkpoint, so the middleware is called again — the at-least-once execution model is visible here exactly as it is.
func NewStepRequest ¶
func NewStepRequest[S any](req *StepRequest, state S) *StepRequest
NewStepRequest returns a shallow copy of req with the state replaced. req is left untouched. S is unchecked here (see NewInitRequest); a mismatch surfaces as ErrInvalidRequest when the strategy's Step is called.
type StepResult ¶
type StepResult struct {
// contains filtered or unexported fields
}
StepResult is what one Step produced: the next state and the Decision.
Both are type-erased for the same reason: a kernel middleware runs across every agent and knows neither S nor the strategy's output type O. Read them with ResultState and ResultDecision, and build a replacement with NewStepResult.
func NewStepResult ¶
func NewStepResult[S, O any](state S, dec Decision[O]) *StepResult
NewStepResult builds a result without calling next, i.e. it decides the transition in place of the strategy. S and O are unchecked here (see NewInitRequest); a mismatch surfaces as ErrInvalidRequest when the state is encoded or the Done output is.
type Strategy ¶
type Strategy[S, I, O any] interface { // Version is the current state schema version. Reading older versions is // DecodeState's job (it absorbed the old Migrate). Version() int // Init builds the initial state, purely. Input is received typed (Spawn // passes the typed value through as any and BindStrategy's closure // type-checks it — no serialization runs). Init receives no Syscalls and no // ctx, so a STRATEGY AUTHOR has structurally no path to effects here. // // That guarantee is about this signature, not about the surrounding call. // Whoever configures the Kernel can wrap Init with InitMiddleware, which does // receive a ctx and can perform effects around it. Init still runs inside // Agent[I].Spawn / SpawnChild and never on the transition machine, so unlike // Step it is free of at-least-once re-execution — which makes it the safer // of the two places for such an effect. Its error is returned synchronously // by Spawn. Init(input I) (S, error) // Step runs one transition. It always receives the DecodeState-restored // state (the first transition too, since Init's result was persisted at // insert). The input I is folded into State and does not appear here. Step(ctx context.Context, sys Syscalls, state S) (S, Decision[O], error) // Limit decides whether this Process may continue. The kernel measures // (Metrics); this method is where the budget policy lives (ADR-0010). It runs // at each transition boundary, before every Generate, CallTool and SpawnChild, // and again after each of those has been metered. // // It takes no S: the boundary evaluation happens before the state is decoded, // and a limit that has to read the algorithm's own state is a branch in Step, // not a budget. What it must be is cheap, read-only and non-blocking — see the // Limiter type, whose shape this method has, for what that rules out. // // Returning LimitPass() means unlimited. There is no way to opt out of // answering, which is the point: a budget nobody configured used to read as no // budget at all. Limit(ctx context.Context, proc *Process, metrics Metrics) LimitDecision // EncodeState / DecodeState fully own state serialization — the format is // free (JSON/gob/protobuf/...). agentkit only stores the bytes. EncodeState(state S) ([]byte, error) DecodeState(version int, raw []byte) (S, error) // EncodeOutput turns the value passed to Done into the bytes stored on // Process.Output. The output must be persisted because a parent reads it // through a children await (await.go), which crosses instance and time // boundaries. There is deliberately no DecodeOutput: nothing reads those // bytes back as O — a completion handler receives the value Done was given // (no round trip), and a parent treats a child's Output as opaque bytes. EncodeOutput(out O) ([]byte, error) }
Strategy is a checkpointable typed state machine. S is the strategy state type, I is the launch input type, O is the output type passed to Done.
type StrategyBinding ¶
type StrategyBinding struct {
// contains filtered or unexported fields
}
StrategyBinding is the type-erased form of a Strategy, storable in a Registry. agentkit itself never touches S, I or O.
func BindStrategy ¶
func BindStrategy[S, I, O any](s Strategy[S, I, O], opts ...RegisterOption[O]) StrategyBinding
BindStrategy erases the type of a Strategy by folding Init/Step/EncodeState/ DecodeState/EncodeOutput into closures, plus the completion handler when one was registered. Limit is carried as the method value itself, having no typed argument to erase. Exported for building fake strategies in tests.
type SubjectRef ¶
SubjectRef is the target of turn-lock (single-flight) suppression.
type Syscalls ¶
type Syscalls interface {
// --- execution context ---
ProcessID() ProcessID
RootID() ProcessID
ParentID() (ProcessID, bool)
Agent() AgentName
Now() time.Time // current time (the Kernel's clock; testable via WithClock). Not deterministic.
// Attempt reports prior attempts at THIS transition that did not commit, so
// a strategy can tell a replay from a first run before acting. A zero value
// means this is the first attempt.
Attempt() AttemptInfo
// Metadata returns a COPY of Process.Metadata (what WithMetadata set at
// spawn), so a strategy can read the process-scoped map without being handed
// the Process. Nil when there is none; writing to the returned map affects
// nothing. It is data, not a credential — the value was trusted when Spawn
// was called, not verified here (ADR-0011).
Metadata() map[string]string
// --- LLM (via gollem; Limit before, Metrics after) ---
Tools() []gollem.Tool // the tools the ToolFactory built (to declare to the LLM).
Generate(ctx context.Context, input []gollem.Input, opts ...GenerateOption) (*GenerateResult, error)
// Session returns the Process's managed conversation, where the runtime
// carries History across calls (and, once committed, across steps and
// workers) and injects the claim's tools. It always returns a usable handle;
// the handle's methods report ErrHistoryNotConfigured when the agent was
// registered without WithHistoryStore. To manage History yourself, use the
// primitive Generate with WithHistory. See ADR-0017.
Session() Session
// --- tool execution (Limit before, Metrics after; no approval gate) ---
CallTool(ctx context.Context, call gollem.FunctionCall) (map[string]any, error)
// --- waits ---
Await(key AwaitKey) (*Await, bool) // reads from the snapshot loaded at transition start. Declaration is via Decision.
// --- observation ---
Emit(ctx context.Context, typ EventType, payload []byte) error // flushed on commit. Encoding is the caller's.
Metrics() Metrics // proc.Metrics (committed) + this run's accumulation.
// LimitStatus reports this Strategy's most recent Limit verdict, starting
// with the one taken at the transition boundary. Switch on Kind() and read
// Message().
//
// It moves in lockstep with Metrics(): Limit runs again before and
// after every Generate, CallTool and SpawnChild, so reading this right after
// a Generate reflects the tokens that Generate just spent — including a
// refusal the Generate itself provoked by crossing the cap.
//
// A LimitKindStop here means Limit is refusing, NOT that this Process
// has stopped. An effect that already ran is not undone, and a strategy is
// free to finish with what it has; enforcement happens at the next effect's
// check or the next transition boundary.
//
// Do NOT fold it into checkpointed state. Like Now() and Metrics(), it
// depends on how far this attempt got and does not reproduce across a replay
// (ADR-0003).
LimitStatus() LimitDecision
// contains filtered or unexported methods
}
Syscalls is the path by which a strategy (a user program) touches the outside world. It runs metering (Metrics) and Limit checks and offers spawn, wait reads, and event emission. The implementation (a private struct) is assembled by the worker per claim. The naming is an OS metaphor: ProcessID()=getpid, Now()=clock, SpawnChild=fork.
There is no effect journal and no approval gate. Generate/CallTool simply call gollem and accumulate Metrics. There is no operation label either — nothing is journaled, so no key is needed to identify a call.
Generate, CallTool and SpawnChild each run through their middleware chain first, if the Kernel was given one. The chain is the outermost layer: it wraps the Limit check, tool resolution and argument validation, and a middleware that returns without calling next stops the call before any of them.
type ToolCallHandler ¶
ToolCallHandler executes one tool call.
type ToolCallMiddleware ¶
type ToolCallMiddleware func(next ToolCallHandler) ToolCallHandler
ToolCallMiddleware wraps a ToolCallHandler.
It can refuse a call fail-closed, and unlike an observation hook it runs before the tool does. It is a real chokepoint for calls made through Syscalls.CallTool — but not the only path to a tool: a strategy holding a gollem.Tool value can call Run on it directly. Enforcement still belongs inside Run; this is not an authorization gate.
type ToolCallRequest ¶
type ToolCallRequest struct {
Effect EffectContext
Call gollem.FunctionCall
}
ToolCallRequest carries one tool call, before the tool is resolved. Rewriting Call.Name selects a different tool; rewriting Call.Arguments changes what is validated and executed.
type ToolFactory ¶
ToolFactory is called once per claim to build the set of tools (gollem.Tool) a Process may use. It is a function type (not an interface) because per-claim construction is the main use and a closure is the most natural form; stateful implementations pass a method value. The implementation decides which tools to give based on proc.Agent / proc.Metadata (the agent kind itself is the selector — the kernel has no selection vocabulary). Process-independent dependencies can be injected via the ctx passed to Serve. The kernel does not interpret proc.
Tools are used as-is: agentkit has no Tool wrapper and no SideEffect class. Side-effect idempotency and any fail-closed authorization are the tool author's responsibility (see the human confirmation pattern; the kernel has no approval gate).
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
historystore
|
|
|
filesystem
Package filesystem is a single-process reference implementation of agentkit.HistoryStore backed by one JSON file per History version.
|
Package filesystem is a single-process reference implementation of agentkit.HistoryStore backed by one JSON file per History version. |
|
historytest
Package historytest is a contract conformance suite for agentkit.HistoryStore implementations.
|
Package historytest is a contract conformance suite for agentkit.HistoryStore implementations. |
|
memory
Package memory is an in-process, non-persistent reference implementation of agentkit.HistoryStore.
|
Package memory is an in-process, non-persistent reference implementation of agentkit.HistoryStore. |
|
repository
|
|
|
filesystem
Package filesystem is a single-process, crash-atomic reference implementation of agentkit.Repository backed by a single JSON snapshot file.
|
Package filesystem is a single-process, crash-atomic reference implementation of agentkit.Repository backed by a single JSON snapshot file. |
|
internal/store
Package store holds the shared in-memory state machine behind the memory and filesystem reference Repository implementations.
|
Package store holds the shared in-memory state machine behind the memory and filesystem reference Repository implementations. |
|
memory
Package memory is an in-process, non-persistent reference implementation of agentkit.Repository.
|
Package memory is an in-process, non-persistent reference implementation of agentkit.Repository. |
|
repotest
Package repotest is a contract conformance suite for agentkit.Repository implementations.
|
Package repotest is a contract conformance suite for agentkit.Repository implementations. |
|
strategy
|
|
|
planexec
Package planexec provides a plan/execute/replan strategy: an LLM planner decomposes the prompt into parallel tasks, each task runs as a child Process (any Agent[T], adapted via makeInput), the results are collected, and the planner decides to iterate or finalize.
|
Package planexec provides a plan/execute/replan strategy: an LLM planner decomposes the prompt into parallel tasks, each task runs as a child Process (any Agent[T], adapted via makeInput), the results are collected, and the planner decides to iterate or finalize. |
|
simple
Package simple provides a general LLM-loop strategy: generate, run any tool calls, feed the results back, repeat until the model answers with no more tool calls.
|
Package simple provides a general LLM-loop strategy: generate, run any tool calls, feed the results back, repeat until the model answers with no more tool calls. |