agent

package
v0.25.5 Latest Latest
Warning

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

Go to latest
Published: Aug 20, 2026 License: MIT Imports: 18 Imported by: 0

Documentation

Overview

Package agent implements the turn loop: one live turn per session, mid-turn steering for overflow messages, bounded worker concurrency, and a system prompt assembled from identity, workspace bootstrap files, drop-in instructions, the skills catalog, and smrti memory recall.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Activity

type Activity struct {
	SessionKey string
	Phase      Phase
	Detail     string
}

Activity is one phase change on one session. Detail carries whatever the phase names: the tool being run, or the line the agent wants the user to read while it works.

type ChatProvider

type ChatProvider interface {
	Chat(ctx context.Context, req *provider.Request) (*provider.Response, error)
}

ChatProvider is what the loop needs from the provider layer (satisfied by *provider.Chain; tests use scripted fakes).

type ContextBuilder

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

ContextBuilder assembles the system prompt. The static portion (identity, bootstrap files, instructions, skill catalog) is cached and invalidated by file mtimes; the memory portion is recalled fresh every turn.

func NewContextBuilder

func NewContextBuilder(cfg *config.Config, loader *skills.Loader, ambient *memory.Ambient) *ContextBuilder

func (*ContextBuilder) SystemPrompt

func (cb *ContextBuilder) SystemPrompt(ctx context.Context, history []provider.Message, current string) string

SystemPrompt builds the full prompt for one turn: identity opens it, the operating rules close it.

Everything in between — the user's workspace files, drop-in instructions, the skills catalog, recalled memories — grows without bound as the agent is used, and recall is weakest in the middle of a long prompt. Rules that must hold on every turn are therefore re-anchored at the tail, the other position that stays reliable. This costs nothing in cache terms: the memory block above already changes every turn, so the cacheable prefix ends before it either way.

type Loop

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

func NewLoop

func NewLoop(cfg *config.Config, b *bus.MessageBus, chat ChatProvider, registry *tools.Registry,
	sessions *session.Store, builder *ContextBuilder, ambient *memory.Ambient) *Loop

func (*Loop) Idle added in v0.7.0

func (l *Loop) Idle() bool

Idle reports whether every session has finished its turn. The gateway asks before restarting itself: a reload mid-turn is an answer the user never gets.

func (*Loop) LastChannel

func (l *Loop) LastChannel() (channel, chatID string, ok bool)

LastChannel returns the most recent external channel/chat, for heartbeat and cron delivery. It survives a restart — the address is on disk — so ok is false until the user's first ever message, and again whenever that chat's connector is not running: an address whose channel was switched off is not somewhere Factor can reach anyone, and a caller that treats it as one reports a delivery that never happened.

func (*Loop) OnActivity

func (l *Loop) OnActivity(fn func(Activity))

OnActivity installs the activity watcher, replacing any previous one and clearing it when fn is nil. It is called from turn goroutines, so it must not block and must not call back into the loop.

func (*Loop) ProcessDirect

func (l *Loop) ProcessDirect(ctx context.Context, content, sessionKey string) (string, error)

ProcessDirect runs a synchronous turn outside the bus (CLI one-shot, cron, delegated jobs) with nobody listening for progress.

func (*Loop) ProcessDirectNotice added in v0.17.1

func (l *Loop) ProcessDirectNotice(ctx context.Context, content, sessionKey string,
	notice func(string)) (string, error)

ProcessDirectNotice runs a synchronous turn outside the bus and reports what the agent says on its way to the answer to notice, as it says it — the connector running the turn is the only place that line can arrive while it is still news. It honors the one-live-turn-per-session invariant: if the session is busy (e.g. an overlapping cron firing), it waits for the claim instead of interleaving histories.

func (*Loop) ProcessEphemeral

func (l *Loop) ProcessEphemeral(ctx context.Context, content string) (string, error)

ProcessEphemeral runs a history-less, memory-less turn (heartbeat).

func (*Loop) Run

func (l *Loop) Run(ctx context.Context)

Run drains the inbound bus until ctx is cancelled. One live turn per session key; overflow becomes steering.

func (*Loop) SetContextWindow added in v0.25.0

func (l *Loop) SetContextWindow(fn func() int)

SetContextWindow teaches the loop how much context the models in play carry, so compaction triggers against the real window instead of an assumption. fn answers 0 when nothing knows — the loop then falls back to config, and past that to a fixed default. It is consulted on every check: the answer can improve after startup, once the model catalog arrives.

func (*Loop) SetReachable added in v0.17.1

func (l *Loop) SetReachable(fn func(channel string) bool)

SetReachable teaches the loop which channels have a connector behind them right now, so LastChannel never hands out an address nothing can deliver to. Unset — the CLI, tests — every channel counts as reachable.

func (*Loop) WaitBackground

func (l *Loop) WaitBackground(timeout time.Duration)

WaitBackground blocks until async work (memory stores, compaction) drains, or the timeout passes. One-shot mode calls this so memory writes are not lost to process exit.

type Phase

type Phase string

Phase is what a turn is busy with at a given moment. Front-ends map these onto whatever they draw while the user waits.

const (
	PhaseContext    Phase = "context"    // assembling the prompt: skills, memory recall
	PhaseThinking   Phase = "thinking"   // waiting on the provider
	PhaseTool       Phase = "tool"       // running a tool call (Detail is its name)
	PhaseNotice     Phase = "notice"     // the agent said what it is about to do (Detail is that line)
	PhaseCompacting Phase = "compacting" // summarizing history after a context overflow
	PhaseSteering   Phase = "steering"   // folding in a message that arrived mid-turn
	PhaseDone       Phase = "done"       // the turn ended, with a reply or an error
)

Jump to

Keyboard shortcuts

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