Documentation
¶
Index ¶
- func BuildToolPool(cwd string, mainTools []agentcore.Tool) []agentcore.Tool
- func CodebotToolClassifier(name string) bool
- func FilterToolsForAgent(in []agentcore.Tool, opts FilterOpts) []agentcore.Tool
- func SessionMemorySeedFn(cwd string) func() (string, error)
- func SubagentHubObserver(hub *TeammateEventHub) func(meta subagent.RunMeta, ev agentcore.Event)
- func TeammateSpawner(reg *team.Registry, rt *task.Runtime, extraTools []agentcore.Tool, ...) subagent.TeamSpawner
- type AgentDefinition
- type AgentInfo
- type AgentSource
- type BuildDeps
- type CacheStats
- type CompactionKind
- type CompactionResult
- type CompactionSnapshot
- type ContextBreakdown
- type ContextSuggestion
- type ErrorSnapshot
- type FilterOpts
- type LeaderInboxPump
- type MessageInjector
- type ModelFactory
- type PlanModeSignal
- type PresenceEvent
- type ReminderSnapshot
- type RuntimeMetricsSnapshot
- type RuntimeReminderKind
- type Session
- func (s *Session) APIKey() string
- func (s *Session) Abort()
- func (s *Session) AbortSilent()
- func (s *Session) AppendGoalState(entry storage.GoalStateEntry) error
- func (s *Session) ApplySkillDelta(name string, delta skill.Delta) error
- func (s *Session) ApplySkillInvocation(result *skill.InvocationResult) error
- func (s *Session) BaseURL() string
- func (s *Session) CacheStats() CacheStats
- func (s *Session) ClearConversation()
- func (s *Session) Close()
- func (s *Session) Compact() (CompactionResult, error)
- func (s *Session) ContextBreakdown() ContextBreakdown
- func (s *Session) ContextSnapshot() (*agentcore.ContextSnapshot, bool)
- func (s *Session) ContextSuggestions() []ContextSuggestion
- func (s *Session) ContextUsage() *agentcore.ContextUsage
- func (s *Session) CostEstimate() (inputTokens, outputTokens int, cost float64)
- func (s *Session) CurrentSessionInfo() (storage.SessionInfo, error)
- func (s *Session) CurrentSnapshot() (storage.ContextSnapshot, error)
- func (s *Session) Diff() (string, error)
- func (s *Session) DynamicSystemBlock() *agentcore.SystemBlock
- func (s *Session) GenerateSuggestion(ctx context.Context) (string, error)
- func (s *Session) HandleGoalChange(change goal.Change)
- func (s *Session) HandleOverflowRewrite(info agentctx.RewriteEvent)
- func (s *Session) HandleProjectedRewrite(info agentctx.RewriteEvent)
- func (s *Session) IsRunning() bool
- func (s *Session) LastAssistantText() string
- func (s *Session) LastCompaction() (CompactionSnapshot, bool)
- func (s *Session) LastReminder() (ReminderSnapshot, bool)
- func (s *Session) LastRunSummary() (agentcore.RunSummary, bool)
- func (s *Session) LastTurnOutcome() TurnOutcomeSnapshot
- func (s *Session) ListSessions() ([]storage.SessionInfo, error)
- func (s *Session) Messages() []agentcore.AgentMessage
- func (s *Session) ModelName() string
- func (s *Session) OverlayPrompt(key, text string)
- func (s *Session) PostSummaryRecoveryHook() agentctx.PostSummaryHook
- func (s *Session) Prompt(text string) error
- func (s *Session) PromptWithBlocks(blocks []agentcore.ContentBlock) error
- func (s *Session) Provider() string
- func (s *Session) RecentErrors(limit int) []ErrorSnapshot
- func (s *Session) RecentToolCalls(limit int) []ToolCallSnapshot
- func (s *Session) Redo() (changed []string, ok bool, err error)
- func (s *Session) Registry() *provider.ModelRegistry
- func (s *Session) Reload()
- func (s *Session) ReplaceMCPTools(tools []agentcore.Tool)
- func (s *Session) Reset() error
- func (s *Session) ResetTaskList() error
- func (s *Session) RestoreAllTools(extra ...agentcore.Tool)
- func (s *Session) RuntimeMetrics() RuntimeMetricsSnapshot
- func (s *Session) SessionID() string
- func (s *Session) SetBeforePrompt(fn func())
- func (s *Session) SetGoalSignal(fn func() goal.Signal)
- func (s *Session) SetGoalUsageLimitHandler(fn func(string) (goal.State, error))
- func (s *Session) SetMCPInstructions(text string)
- func (s *Session) SetModel(prov, model string) error
- func (s *Session) SetPlanModeSignal(fn func() PlanModeSignal)
- func (s *Session) SetSkillCatalog(catalog *skill.Catalog)
- func (s *Session) SetTaskNotifyFn(fn storage.TaskNotifyFn)
- func (s *Session) SetThinkingLevel(level agentcore.ThinkingLevel)
- func (s *Session) SetTools(tools ...agentcore.Tool)
- func (s *Session) Settings() config.Resolved
- func (s *Session) SideQuestion(ctx context.Context, question string) (string, error)
- func (s *Session) SkillCatalog() *skill.Catalog
- func (s *Session) Skills() []skill.Spec
- func (s *Session) SnapshotEnabled() bool
- func (s *Session) Steer(text string)
- func (s *Session) Subscribe(fn func(SessionEvent)) func()
- func (s *Session) SwitchSession(id string) error
- func (s *Session) TaskSnapshot() storage.TaskSnapshot
- func (s *Session) ToolsByName(names ...string) []agentcore.Tool
- func (s *Session) TotalTokens() int
- func (s *Session) Undo() (changed []string, ok bool, err error)
- type SessionConfig
- type SessionEvent
- type SessionEventType
- type SessionMemory
- type Snapshotter
- type TeammateEventHub
- func (h *TeammateEventHub) ActiveAgents() []string
- func (h *TeammateEventHub) IsActive(agentName string) bool
- func (h *TeammateEventHub) KnownAgents() []AgentInfo
- func (h *TeammateEventHub) MarkStopped(agentName string)
- func (h *TeammateEventHub) Publish(agentName string, ev agentcore.Event)
- func (h *TeammateEventHub) Subscribe(agentName string) ([]agentcore.Event, <-chan agentcore.Event, func())
- func (h *TeammateEventHub) SubscribePresence() (<-chan PresenceEvent, func())
- type TeammatePersist
- type TeammateWaker
- type ToolCallSnapshot
- type ToolTokenUsage
- type TurnOutcomeSnapshot
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func BuildToolPool ¶ added in v0.1.3
BuildToolPool returns a tool list suitable as input to FilterToolsForAgent. Read / write / edit are replaced with fresh instances sharing a sub-agent- local FileReadState so a read in a sub-agent cannot poison the parent's read-stamp cache (which would let the parent's next write of the same file silently skip its own read-before-write check). Other tools are passed through by reference — they are either stateless or already keyed by cwd.
Call this ONCE PER SUB-AGENT KIND. Two sub-agent kinds (e.g. explore and general-purpose) must NOT share the returned slice: doing so re-introduces the same cross-pollination of read state that we created this function to prevent.
func CodebotToolClassifier ¶ added in v0.1.0
func FilterToolsForAgent ¶ added in v0.1.3
func FilterToolsForAgent(in []agentcore.Tool, opts FilterOpts) []agentcore.Tool
FilterToolsForAgent returns the subset of `in` that a sub-agent may use.
Checks per tool, in order:
- The `subagent` tool itself is dropped (recursive spawn guard, applied unconditionally — not configurable).
- MCP tools (mcp__ prefix) pass through iff AllowMCP, bypassing the allow/deny lists.
- allAgentDisallowed is dropped.
- customAgentDisallowed is dropped when !IsBuiltIn.
- ExtraDisallowed (per-agent) is dropped.
- When IsAsync, only asyncAgentAllowed passes; everything else is dropped.
func SessionMemorySeedFn ¶ added in v0.1.1
SessionMemorySeedFn returns a closure suitable for plugging into agentcore's SessionMemoryStrategy. The closure reads the project-scoped memory file on each compaction attempt and returns the empty string when the file is missing or still matches the initial template — both of which signal "no useful memory yet, fall through to LLM summarization".
func SubagentHubObserver ¶ added in v0.2.2
func SubagentHubObserver(hub *TeammateEventHub) func(meta subagent.RunMeta, ev agentcore.Event)
SubagentHubObserver builds the subagent.Tool event observer that fans every sub-agent run's raw AgentLoop events into the teammate event hub. This is what makes one-shot / background sub-agents observable in the live-preview modal the same way long-lived teammates already are — one hub, two producers.
Naming: the hub keys by a human-readable display name, but RunMeta.Agent is the agent TYPE (e.g. "explore"), which collides when the same type runs concurrently (parallel mode) or alongside a same-named teammate. We assign each unique RunMeta.InstanceID a display name on its first event, picking the bare type when free and appending " #2", " #3", … otherwise — dedup mirrors uniqueAgentName but resolves against names currently live in the hub plus the ones this observer has already handed out. The mapping is released on the run's EventAgentEnd (guaranteed on every termination path) so names recycle.
Returns nil when hub is nil so callers can wire unconditionally.
func TeammateSpawner ¶ added in v0.2.0
func TeammateSpawner(reg *team.Registry, rt *task.Runtime, extraTools []agentcore.Tool, hub *TeammateEventHub, baseBlocks []agentcore.SystemBlock, dynamicProvider func() *agentcore.SystemBlock, protocol team.ProtocolHooks, hookRunner *hooks.Runner, persist *TeammatePersist) subagent.TeamSpawner
TeammateSpawner returns the subagent.TeamSpawner closure that turns a `subagent { team_name: ... }` tool call into a long-lived teammate. Bound to the runtime's team registry + task runtime so every spawn shares the same coordination surface (send_message / leader inbox pump).
Parameters:
- extraTools: force-injected on top of req.Config.Tools (send_message and the shared task tools) — listed explicitly to avoid leaking leader-only tools.
- hub: per-session teammate event fan-out; nil disables observation.
- baseBlocks: universal base prefix shared with the leader for prompt cache reuse in Default/Append modes; nil ⇒ role block only.
- dynamicProvider: invoked once per spawn to snapshot the leader's current dynamic block (MCP + overlays). Snapshot is frozen at spawn; nil skips dynamic propagation.
- protocol: fully wired ProtocolHooks (envelope, idle notification, priority, optional IdleClaim). Bootstrap owns the wiring.
- hookRunner: fires the SubagentStop lifecycle hook when a teammate exits; nil disables it.
- persist: durable roster + transcript stores. On a successful spawn the teammate is recorded in the roster and every turn is appended to its transcript, so a restart can re-spawn it with its prior context. nil disables persistence.
Types ¶
type AgentDefinition ¶ added in v0.1.3
type AgentDefinition struct {
// Name is the unique identifier the LLM passes to the subagent tool.
// Must be non-empty and globally unique after merging — MergeAgents
// resolves name collisions by source precedence (user > project > builtin).
Name string
// Description is shown to the LLM in the subagent tool's schema enum
// description. Treat it like a docstring: it tells the model WHEN to
// invoke this agent, not what the agent does internally.
Description string
// SystemPrompt is the agent's system prompt. For built-ins it is a Go
// string literal; for file-loaded agents it is the markdown body that
// follows the frontmatter.
SystemPrompt string
// Tools is an allow-list of tool names. nil or {"*"} means "everything
// the filter rules allow"; otherwise the listed names are intersected
// with the filter output. Used by file-loaded agents to scope the
// surface further than the global filter does.
Tools []string
// DisallowedTools is a per-agent deny-list, applied AFTER the global
// rules. Mapped to FilterOpts.ExtraDisallowed at BuildConfig time.
DisallowedTools []string
// Model is "inherit" (use the parent's model), an explicit model name,
// or empty (defaults to inherit).
Model string
// MaxTurns caps the agent's loop. Zero means use the subagent default.
MaxTurns int
// Background, when true, marks the agent as one that should run async
// by default. The subagent tool can still override this per call via
// its `background` parameter.
Background bool
// Provenance — set by the loader, read by tooling that wants to point
// the user at a definition's origin (`/agents show explore` etc.).
Source AgentSource
BaseDir string // directory the agent was loaded from, or "builtin"
Filename string // file path within BaseDir, or "" for built-ins
}
AgentDefinition is codebot's spec layer for a sub-agent. It carries every piece of information needed to materialise a subagent.Config, plus provenance metadata used by callers, the UI, and `/agents` listings.
Three places construct AgentDefinitions:
- Built-in defaults (see builtinAgentDefinitions)
- agent_loader.LoadAgentsDir reading .codebot/agents/*.md frontmatter
- (Future) plugin contributions
All three feed MergeAgents and then BuildConfig in turn.
func BuiltinDefinitions ¶ added in v0.1.3
func BuiltinDefinitions(cwd string) []AgentDefinition
BuiltinDefinitions returns the three sub-agents that ship with codebot.
They use the same AgentDefinition shape that file-loaded agents use, so the bootstrap pipeline can treat builtin / project / user agents uniformly. Two practical consequences:
- A user can override `explore` by dropping their own .codebot/agents/explore.md into the project. The MergeAgents step picks up the later source by name.
- Built-ins go through the same Validate() and BuildConfig() pipeline as everything else, so a typo in the prompt loader can't produce a different runtime shape from a typo in a Go literal.
If you find yourself adding a fourth built-in, ask first whether it should be a built-in at all — a project-level agent in .codebot/agents/ is usually the more honest place for codebase-specific roles.
func LoadAgentsDir ¶ added in v0.1.3
func LoadAgentsDir(dir string, source AgentSource) (defs []AgentDefinition, errs []error)
LoadAgentsDir reads every *.md file under dir and parses them as agent definitions. Files that fail to parse are reported but do not abort the load — a single broken file should not block the user from using the rest of their agent library. The returned errors slice has one entry per broken file; the returned definitions slice excludes those files.
dir is allowed to not exist (returns nil, nil) — the loader is happy to be called speculatively against directories that haven't been created yet.
func MergeAgents ¶ added in v0.1.3
func MergeAgents(groups ...[]AgentDefinition) []AgentDefinition
MergeAgents combines definitions from multiple sources, with later groups overriding earlier ones by name. The expected call order is:
MergeAgents(builtin, project, user)
so a user file overrides a project file overrides a built-in. This matches the trust-vs-customisability ordering: ship sensible defaults, let teams override per-project, let individuals override per-machine.
IMPORTANT: override is WHOLE-DEFINITION replacement, not field-level merge. A user file that re-declares `explore` but omits `disallowedTools` will drop the read-only restriction the built-in version had. This is intentional — field-level merging is hard to predict ("did I inherit X from project or builtin?") and we'd rather force the user to be explicit than make them debug surprising privilege escalations. Document this in any user-facing agent-authoring guide.
Empty names are skipped silently — Validate() should catch them upstream; MergeAgents is the wrong place to surface schema errors.
func (*AgentDefinition) BuildConfig ¶ added in v0.1.3
func (d *AgentDefinition) BuildConfig(deps BuildDeps, ctxFactory func(agentcore.ChatModel) agentcore.ContextManager) (subagent.Config, error)
BuildConfig converts an AgentDefinition into a subagent.Config ready to be passed to subagent.New(...). It is responsible for:
- Resolving the model name to a ChatModel (inherit vs explicit).
- Calling BuildToolPool to give the sub-agent its own read/write/edit instances (independent FileReadState — see Stage 2 review).
- Running FilterToolsForAgent with rules derived from Source/Background.
- Applying AgentDefinition.Tools as a post-filter allow-list when set.
- Wiring a fresh ContextManager via the factory the caller supplied.
BuildConfig is pure with respect to BuildDeps: calling it twice with the same definition and deps yields independent subagent.Config values (which is what the per-agent FileReadState invariant requires).
func (*AgentDefinition) Validate ¶ added in v0.1.3
func (d *AgentDefinition) Validate() error
Validate checks the structural invariants we can verify without the world — required fields present, tool-name strings non-empty, etc. Returns the first error encountered. Loader call sites must invoke this after parsing.
type AgentInfo ¶ added in v0.2.0
AgentInfo describes a known teammate: its name and whether it is still publishing events. Returned by KnownAgents so the UI can render an "ended" indicator without a second round-trip.
type AgentSource ¶ added in v0.1.3
type AgentSource string
AgentSource is the provenance of an AgentDefinition. It maps to a trust tier: built-in is code-controlled and trusted; project lives in version control (team-reviewed); user is per-machine and the least scrutinised.
The source is consulted at tool-pool assembly time — see FilterToolsForAgent's IsBuiltIn flag — so adding a new source means deciding whether sub-agents loaded from it should be subject to customAgentDisallowed.
const ( SourceBuiltin AgentSource = "builtin" SourceProject AgentSource = "project" // .codebot/agents/ SourceUser AgentSource = "user" // ~/.codebot/agents/ )
func (AgentSource) IsBuiltIn ¶ added in v0.1.3
func (s AgentSource) IsBuiltIn() bool
IsBuiltIn reports whether the source qualifies for the built-in trust tier in tool filtering. Today only SourceBuiltin does; SourceProject is kept out because a project agent file changes via PR review but executes on every collaborator's machine — different threat model from code that shipped with the binary.
type BuildDeps ¶ added in v0.1.3
type BuildDeps struct {
// Cwd is the workspace root. Used by sub-agent prompts and by the
// per-agent tool pool when constructing scoped read/write/edit
// instances.
Cwd string
// MainTools is the parent agent's tool list AS HANDED TO buildSubAgentTool
// — that is, before the subagent and Skill tools are appended. Every
// AgentDefinition gets this list re-pooled into independent instances
// inside BuildConfig (see BuildToolPool's contract).
MainTools []agentcore.Tool
// DefaultModel is the parent agent's chat model, used when the
// AgentDefinition either omits Model or sets it to "inherit".
DefaultModel agentcore.ChatModel
// ContextWindow is the parent's context window size, threaded to the
// per-agent ContextManager so summary thresholds align with the model
// in use.
ContextWindow int
// ResolveModel maps an explicit model name from AgentDefinition.Model
// to a ChatModel. May be nil when the build pipeline doesn't support
// per-agent model override; in that case any explicit Model that
// isn't "inherit" produces an error from BuildConfig.
ResolveModel func(name string) (agentcore.ChatModel, error)
}
BuildDeps carries the runtime context BuildConfig needs to materialise an AgentDefinition into a subagent.Config. It is constructed once per call to buildSubAgentTool and reused across every definition.
Why a struct instead of positional args: BuildConfig is called from bootstrap, and bootstrap already has eight parameters threading through subagent assembly. A struct lets us add fields (e.g. eventual MCP server inheritance) without breaking call sites.
type CacheStats ¶ added in v0.1.3
type CacheStats struct {
Input int
ReadTokens int
WriteTokens int
HitRate float64
SavedUSD float64
}
CacheStats reports session-cumulative prompt cache metrics. Input includes CacheRead per litellm convention; HitRate is CacheRead / Input. SavedUSD estimates the dollars saved by serving CacheRead tokens at the cache-read rate instead of the full input rate.
type CompactionKind ¶ added in v0.1.0
type CompactionKind string
const ( CompactionKindMicro CompactionKind = "micro" CompactionKindFull CompactionKind = "full" CompactionKindTrim CompactionKind = "trim" CompactionKindPrune CompactionKind = "prune" )
type CompactionResult ¶ added in v0.0.2
type CompactionSnapshot ¶ added in v0.1.0
type ContextBreakdown ¶ added in v0.1.0
type ContextBreakdown struct {
UserText int
AssistantText int
ToolCalls int
ToolResults int
Summaries int
Images int
Total int
ContextWindow int
// TopTools lists the heaviest tools by total (call + result) tokens.
TopTools []ToolTokenUsage
}
ContextBreakdown reports per-category token distribution in the conversation.
type ContextSuggestion ¶ added in v0.1.0
type ContextSuggestion struct {
Severity string // "warning" or "info"
Message string
Savings int // estimated token savings, 0 if unknown
}
ContextSuggestion is an actionable recommendation to reduce context usage.
type ErrorSnapshot ¶ added in v0.1.0
type FilterOpts ¶ added in v0.1.3
type FilterOpts struct {
// IsBuiltIn is true when the agent is defined in code, false when loaded
// from a user-controlled source (.codebot/agents/, plugins).
IsBuiltIn bool
// IsAsync is true for background-mode runs. Async agents cannot prompt
// the user, so they are restricted to the asyncAgentAllowed list.
IsAsync bool
// AllowMCP controls whether MCP tools (mcp__*) pass through. Defaults
// false — call sites that want MCP must opt in.
AllowMCP bool
// ExtraDisallowed is a per-agent denylist applied on top of the global
// rules. Used by built-in agents to scope themselves further (e.g. the
// explore agent excludes write/edit/bash to stay read-only).
ExtraDisallowed []string
}
FilterOpts controls how FilterToolsForAgent selects tools for a sub-agent.
type LeaderInboxPump ¶ added in v0.2.0
type LeaderInboxPump struct {
// contains filtered or unexported fields
}
LeaderInboxPump bridges the leader's mailbox to the leader agent. Without it, messages routed by send_message to "team-lead" land in the mailbox but never reach the model — there is no equivalent of the teammate runner loop for the main agent (it is driven by the user / TUI session instead).
The pump:
- sleeps in short backoff while no team is active (team_create wires up the leader mailbox only when invoked);
- subscribes to the leader mailbox via Wait and drains arriving messages;
- filters out idle_notification envelopes (they exist to wake the leader for fan-out coordination, but the model itself should not see the JSON);
- calls Agent.Inject for every remaining message so the agent steers / resumes / queues based on its current state.
Lifecycle: spawned at boot, exits when ctx is cancelled (Runtime.Close).
func NewLeaderInboxPump ¶ added in v0.2.0
func NewLeaderInboxPump(reg *team.Registry, ag MessageInjector, waitInterval time.Duration) *LeaderInboxPump
NewLeaderInboxPump constructs a pump for the given registry + leader agent. A zero waitInterval picks the default. Both reg and ag must be non-nil.
func (*LeaderInboxPump) Run ¶ added in v0.2.0
func (p *LeaderInboxPump) Run(ctx context.Context)
Run blocks until ctx is cancelled. Safe to call as `go pump.Run(ctx)` from bootstrap. The loop is two-phase:
- No team yet: short timer-driven backoff until Registry.Mailbox returns non-nil for TeamLeadName.
- Team active: Wait on the mailbox, Drain on wake, Inject each non-control message. On ErrClosed (team torn down) fall back to phase 1.
type MessageInjector ¶ added in v0.2.0
type MessageInjector interface {
Inject(agentcore.AgentMessage) (agentcore.InjectResult, error)
}
MessageInjector is the slice of *agentcore.Agent the pump actually uses. Defined as an interface so tests can stand in a fake without spinning the full agent machinery.
type ModelFactory ¶
ModelFactory creates a chat model instance for a provider/model tuple.
type PlanModeSignal ¶ added in v0.1.3
PlanModeSignal describes the plan-mode state observed at the moment the runtime polls before queueing a per-prompt reminder. Three distinct situations need different behavior:
- Active=true: plan mode is on; emit the sparse "still read-only" reminder on its 5-turn cadence (PlanFilePath gives the writable plan file).
- JustCancelled=true: plan mode was cancelled via /plan cancel since the last poll; emit a one-shot "you have exited plan mode" reminder so the model knows the MUST-NOT rules buried in the EnterPlanMode tool_result no longer apply.
- All zero: plan mode is off; no reminder needed.
Active and JustCancelled are mutually exclusive — the producer (plan.Manager) guarantees that. JustCancelled is consumed on read: a second poll without a fresh Cancel() returns the zero value.
type PresenceEvent ¶ added in v0.2.0
PresenceEvent describes a teammate joining or leaving the hub. Started is emitted on the first Publish for an agent; Stopped is emitted by an explicit MarkStopped call (the spawner invokes this when the teammate's goroutine exits, so subscribers can release resources without polling).
type ReminderSnapshot ¶ added in v0.1.0
type ReminderSnapshot struct {
Kind RuntimeReminderKind
Mode string
Timestamp time.Time
}
type RuntimeMetricsSnapshot ¶ added in v0.1.0
type RuntimeMetricsSnapshot struct {
ReminderTotal int
ReminderByKind map[RuntimeReminderKind]int
CompactionTotal int
CompactionChanged int
CompactionSaved int
CompactionByKind map[CompactionKind]int
CompactionSavedByKind map[CompactionKind]int
ErrorTotal int
ErrorByCategory map[diag.Category]int
}
type RuntimeReminderKind ¶ added in v0.1.0
type RuntimeReminderKind string
const ( ReminderRepeatToolCall RuntimeReminderKind = "repeat_tool_call" ReminderPostStopValidation RuntimeReminderKind = "post_stop_validation" ReminderSkillPaths RuntimeReminderKind = "skill_paths" ReminderTaskManagement RuntimeReminderKind = "task_management" ReminderPlanMode RuntimeReminderKind = "plan_mode" ReminderGoal RuntimeReminderKind = "goal" ReminderHookContext RuntimeReminderKind = "hook_context" )
type Session ¶
type Session struct {
// contains filtered or unexported fields
}
Session is the business-logic core that wraps Agent + session persistence. It is independent of any UI framework and drives interactive, print, and RPC modes.
func NewSession ¶
func NewSession(cfg SessionConfig) *Session
NewSession creates a Session and wires auto-persist to the agent.
func (*Session) AbortSilent ¶
func (s *Session) AbortSilent()
func (*Session) AppendGoalState ¶ added in v0.2.2
func (s *Session) AppendGoalState(entry storage.GoalStateEntry) error
func (*Session) ApplySkillDelta ¶ added in v0.1.0
func (*Session) ApplySkillInvocation ¶ added in v0.1.0
func (s *Session) ApplySkillInvocation(result *skill.InvocationResult) error
func (*Session) CacheStats ¶ added in v0.1.3
func (s *Session) CacheStats() CacheStats
func (*Session) ClearConversation ¶
func (s *Session) ClearConversation()
func (*Session) Compact ¶
func (s *Session) Compact() (CompactionResult, error)
func (*Session) ContextBreakdown ¶ added in v0.1.0
func (s *Session) ContextBreakdown() ContextBreakdown
ContextBreakdown computes a per-category token breakdown of the current conversation messages. It does NOT include system prompt or tool definitions.
func (*Session) ContextSnapshot ¶ added in v0.1.0
func (s *Session) ContextSnapshot() (*agentcore.ContextSnapshot, bool)
func (*Session) ContextSuggestions ¶ added in v0.1.0
func (s *Session) ContextSuggestions() []ContextSuggestion
ContextSuggestions generates actionable suggestions based on context usage and the message breakdown.
func (*Session) ContextUsage ¶
func (s *Session) ContextUsage() *agentcore.ContextUsage
func (*Session) CostEstimate ¶
func (*Session) CurrentSessionInfo ¶
func (s *Session) CurrentSessionInfo() (storage.SessionInfo, error)
func (*Session) CurrentSnapshot ¶ added in v0.2.2
func (s *Session) CurrentSnapshot() (storage.ContextSnapshot, error)
func (*Session) Diff ¶ added in v0.2.0
Diff returns a numstat preview of what Undo would roll back (the last turn's file changes), or "" when there is nothing to undo.
func (*Session) DynamicSystemBlock ¶ added in v0.2.0
func (s *Session) DynamicSystemBlock() *agentcore.SystemBlock
DynamicSystemBlock returns the current dynamic block snapshot (MCP tool inventory + active overlays), or nil when there's nothing to include.
Called by the teammate spawner at spawn time so teammates inherit the leader's MCP descriptions and overlay state. Each teammate freezes its system prompt at spawn — later leader-side changes (plan-mode toggle / MCP refresh) do NOT propagate to already-running teammates.
The block intentionally carries no CacheControl. Two reasons:
- Anthropic caps system-field cache_control markers; the universal base and role block already consume two — leaving the dynamic block uncached keeps headroom for the message-level marker the agent loop adds.
- The dynamic block can churn mid-session (plan-mode toggle, MCP reconnect); a cache breakpoint here would be invalidated frequently and would charge cache-write cost for short-lived content.
func (*Session) GenerateSuggestion ¶ added in v0.0.4
GenerateSuggestion calls the current model with a condensed conversation history plus a suggestion prompt to predict the user's next input. Returns "" when no suggestion is available (first turn, no model, etc.).
func (*Session) HandleGoalChange ¶ added in v0.2.2
func (*Session) HandleOverflowRewrite ¶ added in v0.1.0
func (s *Session) HandleOverflowRewrite(info agentctx.RewriteEvent)
func (*Session) HandleProjectedRewrite ¶ added in v0.1.0
func (s *Session) HandleProjectedRewrite(info agentctx.RewriteEvent)
func (*Session) LastAssistantText ¶
func (*Session) LastCompaction ¶ added in v0.1.0
func (s *Session) LastCompaction() (CompactionSnapshot, bool)
func (*Session) LastReminder ¶ added in v0.1.0
func (s *Session) LastReminder() (ReminderSnapshot, bool)
func (*Session) LastRunSummary ¶ added in v0.1.0
func (s *Session) LastRunSummary() (agentcore.RunSummary, bool)
func (*Session) LastTurnOutcome ¶ added in v0.1.0
func (s *Session) LastTurnOutcome() TurnOutcomeSnapshot
func (*Session) ListSessions ¶
func (s *Session) ListSessions() ([]storage.SessionInfo, error)
func (*Session) Messages ¶ added in v0.0.2
func (s *Session) Messages() []agentcore.AgentMessage
Messages returns the current agent message history.
func (*Session) OverlayPrompt ¶ added in v0.1.1
OverlayPrompt registers or removes a named instructions overlay. Pass empty text to remove. Overlays are rendered in insertion order.
func (*Session) PostSummaryRecoveryHook ¶ added in v0.1.0
func (s *Session) PostSummaryRecoveryHook() agentctx.PostSummaryHook
func (*Session) PromptWithBlocks ¶
func (s *Session) PromptWithBlocks(blocks []agentcore.ContentBlock) error
func (*Session) RecentErrors ¶ added in v0.1.0
func (s *Session) RecentErrors(limit int) []ErrorSnapshot
func (*Session) RecentToolCalls ¶ added in v0.1.0
func (s *Session) RecentToolCalls(limit int) []ToolCallSnapshot
func (*Session) Redo ¶ added in v0.2.0
Redo re-applies the most recently undone turn's file changes. ok is false when there is nothing to redo (no tracker, no prior undo, or the redo branch was invalidated by a new turn).
func (*Session) Registry ¶
func (s *Session) Registry() *provider.ModelRegistry
func (*Session) ReplaceMCPTools ¶ added in v0.1.0
func (*Session) Reset ¶ added in v0.1.1
Reset closes the current session log and starts a fresh session in the same cwd. The in-memory conversation and harness state are cleared; the previous file is flushed.
func (*Session) ResetTaskList ¶ added in v0.1.0
func (*Session) RestoreAllTools ¶
func (*Session) RuntimeMetrics ¶ added in v0.1.0
func (s *Session) RuntimeMetrics() RuntimeMetricsSnapshot
func (*Session) SetBeforePrompt ¶
func (s *Session) SetBeforePrompt(fn func())
func (*Session) SetGoalSignal ¶ added in v0.2.2
SetGoalSignal registers a callback the runtime polls at natural stop points to decide whether an explicit /goal should auto-continue.
func (*Session) SetGoalUsageLimitHandler ¶ added in v0.2.2
func (*Session) SetMCPInstructions ¶ added in v0.1.0
func (*Session) SetPlanModeSignal ¶ added in v0.1.3
func (s *Session) SetPlanModeSignal(fn func() PlanModeSignal)
SetPlanModeSignal registers a callback the runtime polls before every user prompt to decide whether to queue a plan-mode reminder. plan.Manager wires this so the reminder cadence stays driven by the actual plan-mode state rather than a side-channel boolean.
func (*Session) SetSkillCatalog ¶ added in v0.1.0
func (*Session) SetTaskNotifyFn ¶ added in v0.1.0
func (s *Session) SetTaskNotifyFn(fn storage.TaskNotifyFn)
func (*Session) SetThinkingLevel ¶
func (s *Session) SetThinkingLevel(level agentcore.ThinkingLevel)
func (*Session) SideQuestion ¶ added in v0.0.4
SideQuestion sends a one-shot question to the current model using the full conversation context but without tool definitions or conversation history mutation. The answer is ephemeral — it never enters the session store. This powers the /btw side-chain Q&A feature.
func (*Session) SkillCatalog ¶ added in v0.1.0
func (*Session) SnapshotEnabled ¶ added in v0.2.0
SnapshotEnabled reports whether workspace snapshots are active for this session. It is false when the workspace isn't a git repository (or the snapshot setting is off), in which case Undo/Redo/Diff are inert no-ops — callers use this to explain the no-op instead of reporting "nothing to do".
func (*Session) Subscribe ¶
func (s *Session) Subscribe(fn func(SessionEvent)) func()
func (*Session) SwitchSession ¶
func (*Session) TaskSnapshot ¶ added in v0.1.0
func (s *Session) TaskSnapshot() storage.TaskSnapshot
func (*Session) TotalTokens ¶
type SessionConfig ¶
type SessionConfig struct {
Agent *agentcore.Agent
ContextManager agentcore.ContextManager
Store *storage.Store
Manager *storage.Manager
Registry *provider.ModelRegistry
Settings config.Resolved
Cwd string
TaskStore *storage.TaskStore
// CreateModel allows tests/integrations to override model construction.
// Defaults to provider.CreateModel when nil.
CreateModel ModelFactory
// LazyPersist buffers user messages and flushes them only when
// an assistant response arrives. Disabled by default for safety.
LazyPersist bool
// ChatModel is the active ChatModel reference.
ChatModel agentcore.ChatModel
// HookRunner fires lifecycle hooks (notification, etc.). Nil when no hooks configured.
HookRunner *hooks.Runner
// Snapshotter records workspace file checkpoints at turn boundaries and
// powers /undo. Nil disables snapshotting (e.g. outside a git repo).
Snapshotter Snapshotter
// Tools is the full set of tools registered with the agent.
// Used by ToolsByName / RestoreAllTools for plan mode filtering.
Tools []agentcore.Tool
// ContextFiles holds loaded context files for dynamic system prompt rebuilding.
// When tools change the prompt is regenerated from these inputs.
ContextFiles config.ContextFiles
// Skills holds loaded skills for system prompt injection and /skill: commands.
Skills []skill.Spec
// SkillCatalog provides indexed skill lookup and reload support.
SkillCatalog *skill.Catalog
// SkillUsage persists cross-session usage statistics for prompt ordering.
SkillUsage *skill.UsageTracker
// DeferredToolsPreamble is injected as the first user message (once).
DeferredToolsPreamble string
// Reminders are <system-reminder> fragments prepended to each user message.
Reminders []string
// PreambleInjected indicates the preamble was already in conversation history (resume).
PreambleInjected bool
// SkillAllowsSetter updates temporary tool allows for the active skill.
SkillAllowsSetter func([]string)
// FileReadState records read timestamps consumed by write/edit Validators.
// Held on the Session so Clear / Reset / SwitchSession can drop stale
// stamps — otherwise the LLM may write based on stamps from a read it
// no longer has in its conversation history.
FileReadState *agenttools.FileReadState
// FrozenIdentity / FrozenInstructions are the process-stable parts of the
// system prompt (block 1 + block 2). Computed once at assembly time and
// reused on every rebuild — never recomputed during the session.
// See config.BuildFrozenSystemParts.
FrozenIdentity string
FrozenInstructions string
// InitialMCPOverlay seeds the mcp overlay when the assembly already knows
// the MCP instructions (e.g. MCP managers that connect synchronously).
// Written directly into the overlay store without triggering a rebuild.
InitialMCPOverlay string
// InitialDynamic is the assembly-time dynamic block text (MCP tool list +
// overlays). Stored on the session so teammate spawn can read the current
// snapshot via DynamicSystemBlock() without re-computing. Updated when
// rebuildPrompt runs (overlay change / MCP refresh).
InitialDynamic string
}
SessionConfig configures a new Session.
type SessionEvent ¶
type SessionEvent struct {
Type SessionEventType
AgentEvent *agentcore.Event
// Session-level fields (populated based on Type)
ModelName string
Provider string
Level agentcore.ThinkingLevel
SessionID string
Error error
Reminder string
ReminderKind RuntimeReminderKind
Goal goal.State
GoalPrevious goal.State
// Retry fields (populated for SEAutoRetryStart / SEAutoRetryEnd)
RetryAttempt int
RetryMax int
RetryDelay time.Duration
RetrySuccess bool
// Compaction reason: "overflow" or "threshold"
CompactionReason string
CompactionKind CompactionKind
CompactionStrategy string
CompactionChanged bool
TokensBefore int
TokensAfter int
CompactedCount int
KeptCount int
SplitTurn bool
}
SessionEvent extends agent events with session-level metadata. When Type == SEAgentEvent, AgentEvent is non-nil.
type SessionEventType ¶
type SessionEventType string
SessionEventType identifies a session-level event.
const ( // SEAgentEvent wraps an agentcore.Event transparently. SEAgentEvent SessionEventType = "agent_event" // Session lifecycle events SEAutoCompactionStart SessionEventType = "auto_compaction_start" SEAutoCompactionEnd SessionEventType = "auto_compaction_end" SEAutoRetryStart SessionEventType = "auto_retry_start" SEAutoRetryEnd SessionEventType = "auto_retry_end" SEModelChanged SessionEventType = "model_changed" SEThinkingChanged SessionEventType = "thinking_changed" SESessionSwitched SessionEventType = "session_switched" SERuntimeReminder SessionEventType = "runtime_reminder" SEGoalUpdated SessionEventType = "goal_updated" SEGoalCleared SessionEventType = "goal_cleared" SEError SessionEventType = "session_error" )
type SessionMemory ¶ added in v0.1.1
SessionMemory is the on-disk shape of a memory file. We write the body as plain markdown (no frontmatter) so the file is readable and editable by a human. UpdatedAt comes from the filesystem mtime, not a stored field.
type Snapshotter ¶ added in v0.2.0
type Snapshotter interface {
// Track records a checkpoint of the current workspace. changed is false
// when nothing changed since the last checkpoint.
Track() (changed bool, err error)
// Undo reverts the workspace to the most recent checkpoint. ok is false
// when there is nothing to undo; changed lists the affected paths.
Undo() (changed []string, ok bool, err error)
// Redo re-applies the most recently undone change. ok is false when there
// is nothing to redo (no prior undo, or a new edit invalidated the branch).
Redo() (changed []string, ok bool, err error)
// DiffTop returns a numstat diff of what Undo would roll back, or "" when
// there is nothing to undo.
DiffTop() (string, error)
// Rebind repoints the tracker at a session's persisted undo stack: it drops
// the in-memory stack and loads whatever statePath holds. Called on session
// switch/new so each session gets its own checkpoint history.
Rebind(statePath string)
// Close waits for any background snapshot maintenance to finish.
Close()
}
Snapshotter captures and restores workspace file checkpoints for /undo. Implemented by internal/snapshot.Tracker and injected at assembly time, so the agent package stays decoupled from the git-shadow implementation.
type TeammateEventHub ¶ added in v0.2.0
type TeammateEventHub struct {
// contains filtered or unexported fields
}
TeammateEventHub is a fan-out point for events produced by teammate agent loops. agentcore.AgentLoop returns a single-consumer channel — the executor drains it for produced-message collection — so anything else that wants to observe a teammate's activity (UI transcript view, log sinks, future analytics) must subscribe here.
Design constraints:
- Publish is hot-path (called once per event by every teammate goroutine). It MUST NOT block on a slow subscriber, or it stalls the AgentLoop that produced the event. Each subscriber gets a buffered chan with a drop-oldest policy.
- Subscribers come and go (modal opens/closes). Subscribe returns an unsubscribe function instead of exposing the underlying map.
- Presence (a teammate started / stopped publishing) is its own broadcast so a UI can auto-focus on the first teammate to come online without polling task.Runtime.
- Late subscribers must see what they missed. Every published event is also appended to a per-agent ring buffer; Subscribe hands back a snapshot before wiring the live channel. The ring outlives MarkStopped so an Observer can open a teammate's transcript after it has finished.
Zero-value safety: a nil *TeammateEventHub is a valid no-op publisher — callers (TeammateSpawner) can be wired before the hub exists in tests.
func NewTeammateEventHub ¶ added in v0.2.0
func NewTeammateEventHub() *TeammateEventHub
NewTeammateEventHub returns an empty hub ready to use.
func (*TeammateEventHub) ActiveAgents ¶ added in v0.2.0
func (h *TeammateEventHub) ActiveAgents() []string
ActiveAgents returns the names that are currently publishing — i.e. have published at least once and have not been MarkStopped'd. For the broader roster (including teammates that already finished) use KnownAgents.
func (*TeammateEventHub) IsActive ¶ added in v0.2.0
func (h *TeammateEventHub) IsActive(agentName string) bool
IsActive reports whether agentName is currently publishing events. Returns false for unknown names and for known-but-stopped teammates.
func (*TeammateEventHub) KnownAgents ¶ added in v0.2.0
func (h *TeammateEventHub) KnownAgents() []AgentInfo
KnownAgents returns every teammate that has ever published an event in this session, alongside its current active flag. Use this for "which teammates can I open in the transcript modal?" — already-finished agents still have a readable history.
func (*TeammateEventHub) MarkStopped ¶ added in v0.2.0
func (h *TeammateEventHub) MarkStopped(agentName string)
MarkStopped emits a Stopped presence event and flips the active flag. The history ring is preserved so an observer can still open this teammate's transcript later. Safe to call multiple times — only the first call after a Started transition broadcasts.
func (*TeammateEventHub) Publish ¶ added in v0.2.0
func (h *TeammateEventHub) Publish(agentName string, ev agentcore.Event)
Publish delivers ev to every current subscriber of agentName and appends it to the per-agent history ring. Non-blocking: if a subscriber's buffer is full, the oldest queued event is dropped to make room — slow consumers lose history, never block the publisher.
The first Publish for a (currently-stopped) agentName also broadcasts a PresenceEvent {Started: true}. "Stopped → publishes again" repeats the broadcast; this is intentional so a UI auto-attaching to active teammates catches a teammate that briefly went idle and resumed.
nil receiver is a no-op so spawner wiring stays simple in tests.
Lock discipline: Publish does ring write + chan sends inside the mutex, in strict serialisation with Subscribe's unsubscribe (which closes the chan). Without that ordering, a subscriber that cancels mid-publish would let us send on a closed chan and panic. The sends themselves are non-blocking (drop-oldest), so holding the lock briefly is fine — the hot path is O(subscribers) channel operations, not I/O.
func (*TeammateEventHub) Subscribe ¶ added in v0.2.0
func (h *TeammateEventHub) Subscribe(agentName string) ([]agentcore.Event, <-chan agentcore.Event, func())
Subscribe registers a listener for agentName's events. Returns the recorded history as a snapshot slice (oldest first) plus a live channel for events arriving after the snapshot was taken. The caller MUST consume the history slice before reading the channel so its transcript renders in order.
The channel is buffered (subBufferSize); the publisher drops the oldest queued event when full. cancel MUST be called when the listener is done — it removes the channel from the routing table and closes it.
func (*TeammateEventHub) SubscribePresence ¶ added in v0.2.0
func (h *TeammateEventHub) SubscribePresence() (<-chan PresenceEvent, func())
SubscribePresence returns a channel that receives a PresenceEvent every time a teammate first publishes (Started) or is marked stopped. Channel is buffered; unsubscribe closes it.
The current active roster is replayed as Started events on the returned channel before any future presence changes — late subscribers don't miss teammates that joined earlier.
type TeammatePersist ¶ added in v0.2.0
type TeammatePersist struct {
Roster *storage.RosterStore
Transcripts *storage.TranscriptStore
}
TeammatePersist bundles the durable stores a spawned teammate writes to so the session can recover its team after a restart: the roster (who is on the team + how to re-spawn them) and the per-teammate conversation transcript. A nil *TeammatePersist — or nil fields within — disables that slice of persistence, so tests and ephemeral sessions can omit it entirely.
type TeammateWaker ¶ added in v0.2.0
type TeammateWaker struct {
// contains filtered or unexported fields
}
TeammateWaker re-spawns a dormant teammate on demand, seeded with its persisted transcript, the first time the leader messages it. Teammate recovery is LAZY and message-driven: a stopped teammate is revived only when a message targets it, never eagerly mass-restored at session startup.
A teammate that exited — graceful completion, crash, or a prior session that ended — leaves two durable traces: its roster entry (who it was + the agent type to rebuild its Config from) and its transcript JSONL (what it had done). When a message targets that name and no live teammate answers to it, Wake rebuilds the teammate from those traces and delivers the message as its opening turn — the message itself IS the resume prompt, so no opening message is fabricated.
Live control-flow state (in-flight tool calls, pending approvals) is not restored; the teammate resumes from its last completed turn.
func NewTeammateWaker ¶ added in v0.2.0
func NewTeammateWaker(spawner subagent.TeamSpawner, configOf func(agentType string) (subagent.Config, bool), registry *team.Registry, roster *storage.RosterStore, transcripts *storage.TranscriptStore) *TeammateWaker
NewTeammateWaker assembles a waker from the same spawn closure teammates are created through (so a woken teammate flows through identical tool injection, transcript recording and roster upsert) plus the durable stores. configOf rebuilds a teammate's subagent.Config from its agent type; registry is used to make wake idempotent under concurrency. A nil spawner, configOf or roster makes Wake a permanent no-op so the caller falls back to its normal not-found handling.
func (*TeammateWaker) Wake ¶ added in v0.2.0
Wake re-spawns the dormant teammate named `name`, seeding it with its persisted transcript and delivering `prompt` as its opening message:
- (true, nil) — name matched a persisted roster member and was re-spawned; the message was delivered as its first turn. Caller reports success.
- (false, nil) — name is not a known persisted teammate, OR a concurrent wake already revived it. Either way the caller re-checks liveness: live ⇒ deliver via the mailbox; still absent ⇒ fall through to not-found.
- (false, err) — name matched a roster member but re-spawn failed (unknown agent type, spawn error); caller surfaces the error.
Wake is idempotent under concurrency: a session-wide lock plus a liveness re-check ensures two parallel messages to the same dormant name revive it once, not as clones.
type ToolCallSnapshot ¶ added in v0.1.0
type ToolTokenUsage ¶ added in v0.1.0
ToolTokenUsage tracks token consumption for a single tool.
Source Files
¶
- agent_build.go
- agent_definition.go
- agent_loader.go
- cache_monitor.go
- compaction_policy.go
- context_analysis.go
- events.go
- plan_reminders.go
- runtime_metrics.go
- runtime_policy.go
- session.go
- session_compaction.go
- session_memory.go
- session_prompt.go
- session_runtime.go
- session_snapshot.go
- session_state.go
- subagent_observer.go
- task_reminders.go
- team_inbox.go
- teammate_events.go
- teammate_spawn.go
- teammate_waker.go
- tool_filter.go