agentkit

package module
v0.1.18 Latest Latest
Warning

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

Go to latest
Published: Sep 2, 2026 License: MIT Imports: 8 Imported by: 0

README

AgentKit

基于 pluginkit 的 Go Agent Harness 运行时。通过 YAML 配置装配 Runner、Platform、Loop、Agent、工具与策略,支持交互式 REPL、自主长跑、子 Agent 委派、MCP 与 headless 守护等多种运行形态。

特性

  • 插件化装配 — 64+ 已注册 Plugin Kind,L0 默认配置 + L1 overlay 按需覆盖
  • Coding Agent 闭环 — 文件读写、Shell、策略审批、Session 持久化(jsonl)
  • 自主运行 — turn 续跑、预算分层、todo/finish 收尾、token 阈值压缩、崩溃恢复
  • 子 Agent 委派agents/*.md 定义子 agent,主 agent 只读回结论
  • 自我学习/learn 管理 memory.md,Grounded Dreaming 巩固短期信号,Skill Workshop 生成可审阅技能提案
  • 网络能力 — HTTP 抓取、Exa 搜索、向用户提问(HIL)
  • Headless 模式 — worker(一次性)、timer(固定间隔)、cron(日历 + 自主排期)
  • Web 管理台 — 装配树编辑、结构诊断、试装配与 build 校验

架构概览

flowchart TB
  subgraph entry["进程入口"]
    CLI["cmd/agent"]
    MGR["-manager Web UI"]
  end

  subgraph spine["Spine"]
    R["Runner"]
    P["Platform<br/>(cli / worker / timer / multiplex)"]
    L["Loop"]
    A["Agent<br/>(coding / …)"]
  end

  subgraph deps["依赖组件"]
    LLM["LLM Provider"]
    TOOLS["Tools Runtime"]
    SESS["Session Store"]
    HOOK["Hooks"]
    POL["Policy / Approval"]
  end

  CLI --> R
  MGR -.->|配置编辑| R
  R --> P
  P --> L
  L --> A
  A --> LLM
  A --> TOOLS
  A --> SESS
  A --> HOOK
  TOOLS --> POL

配置模型:config.base.yaml(L0 随仓库发布)+ config.yamlpresets/*.yaml(L1 overlay,后者覆盖前者)。实例图由 pluginkit/build 构造,root 通常为 runner

快速开始

环境要求
  • Go 1.26+
  • OpenAI 兼容 API Key(冒烟 preset 可跳过)
安装与运行
git clone https://github.com/lengzhao/agentkit.git
cd agentkit

export OPENAI_API_KEY=sk-...

# 交互式 REPL
go run ./cmd/agent

# 带首条消息进入 REPL
go run ./cmd/agent "帮我看看这个项目结构"

# 项目目录 coding preset
go run ./cmd/agent -config presets/coding.yaml "你的任务"

# 无 API Key 本地冒烟(scripted LLM)
go run ./cmd/agent -config presets/coding-smoke.yaml "列出当前目录并读取 README"
本地配置

复制示例配置为 L1 override(已在 .gitignore 中):

cp config.example.yaml config.yaml

-config 支持逗号分隔的多个 overlay,按顺序合并,后面的覆盖前面的:

go run ./cmd/agent -config presets/autonomous.yaml,presets/worker.yaml "一次性任务"

常用场景

场景 命令示例
交互式 coding go run ./cmd/agent -config presets/coding.yaml
自主长跑 go run ./cmd/agent -config presets/autonomous.yaml "多轮任务"
子 Agent 委派 go run ./cmd/agent "让 researcher 调研 …"(L0 默认;冒烟见 presets/subagent-smoke.yaml
自我学习 REPL 内执行 /learn/learn dream run/learn skill 部署检查清单
网络搜索 + 抓取 export TAVILY_API_KEY=...-config presets/web.yaml
Headless 批处理 -config presets/autonomous.yaml,presets/worker.yaml
定时守护 -config presets/autonomous.yaml,presets/cron.yaml
配置管理 Web UI go run ./cmd/agent -manager -addr :8080

完整 preset 索引见 presets/README.md

项目结构

agentkit/
├── cmd/agent/          # 主入口(REPL / headless / -manager)
├── config.base.yaml    # L0 默认装配
├── presets/            # 场景 L1 overlay
├── runtime/            # Runner、Loop、Agent、Session、Platform、LLM
├── plugins/            # 工具、Hook、Policy、Prompt 等插件实现
├── cap/                # 能力接口(filesystem、workspace、compaction …)
├── examples/
│   ├── agents/       # 子 Agent 定义示例
│   └── skills/       # Agent Skill 示例(插件开发与配置更新)
└── docs/               # 架构与设计文档(中文)

文档

详细设计文档见 docs/README.zh.md。建议阅读顺序:

flowchart LR
  A["plugin-catalog<br/>插件边界"] --> B["go-agent-harness-architecture<br/>实现细节"]
  B --> C["roadmap<br/>现状与规划"]
文档 说明
go-agent-harness-architecture.zh.md 完整架构:Runner、Spine、装配模型、生命周期
plugin-catalog.zh.md Plugin Kind 目录与分阶段落地
roadmap.zh.md 现状基线与路线图
guides/learning-dreaming.zh.md 自我学习:Dreaming、Dream Diary、Skill Workshop
guides/ 场景专题:自主运行、多租户、工具、人机交互等

开发

# 运行测试
go test ./...

# 新增插件后更新 blank import
go generate ./...

# 查看日志(默认写入 ~/.agentkit/agent.log)
tail -f ~/.agentkit/agent.log

插件通过 pluginkit.Register(kind, New) 注册;构造函数支持 (Config, Deps) 形态,依赖由配置中的 deps 字段注入。

外部参考

项目 说明
pluginkit 插件注册与实例图构建
DeepSeek Harness Agent Harness 参考实现
Pi Coding Agent 扩展模型参考

License

MIT

Documentation

Index

Constants

View Source
const (
	// KeySessionID is the context key for the current opaque SessionID.
	KeySessionID contextKey = "agentkit.session_id"
	// KeyAgentID is the context key for the current AgentID.
	KeyAgentID contextKey = "agentkit.agent_id"
	// KeyPlatformID is the context key for the current inbound/outbound platform.
	KeyPlatformID contextKey = "agentkit.platform_id"
	// KeyUserID is the context key for the current end-user identity, when known.
	KeyUserID contextKey = "agentkit.user_id"
	// KeyTurnID is the context key for the current turn, when one is active.
	KeyTurnID contextKey = "agentkit.turn_id"
	// KeyToolCallID is the context key for the current tool call, when one is active.
	KeyToolCallID contextKey = "agentkit.tool_call_id"
	// KeySessionControl is the context key for per-session steer/follow-up state.
	// Loop sets it before Agent.RunTurn; the value is a runtime/loop.Control that
	// also implements permission.Broker.
	KeySessionControl contextKey = "agentkit.session_control"
	// KeyOutboundEmit is the per-turn outbound hook. Loop sets it before
	// Agent.RunTurn so tools and permission waits can emit outbound events
	// through the same channel as assistant streaming.
	KeyOutboundEmit contextKey = "agentkit.outbound_emit"
	// KeyDeliverySessionID is the inbound delivery SessionID for the current turn
	// (finest grain). Outbound routing should prefer this over KeySessionID when
	// both are present.
	KeyDeliverySessionID contextKey = "agentkit.delivery_session_id"
	// KeyStoreSessionID is the logical SessionID used for model-visible history.
	// It may differ from KeySessionID when a stable IM delivery/effective key has
	// been switched to a fresh history with /new.
	KeyStoreSessionID contextKey = "agentkit.store_session_id"
	// KeyInSubagent marks a context running inside a delegated child agent. While
	// set, session append/recovery must target KeySessionID only and must not
	// inherit a parent's KeyStoreSessionID mapping.
	KeyInSubagent contextKey = "agentkit.subagent.active"
	// KeyMessageMetadata is optional platform metadata for the current inbound turn.
	KeyMessageMetadata contextKey = "agentkit.message_metadata"
	// KeyProactiveSendUsed is set when tool/send delivers through the turn emit
	// channel during the current turn.
	KeyProactiveSendUsed contextKey = "agentkit.proactive_send_used"
	// KeyScheduleFireTurn marks a turn started by schedule runtime. Turn-end
	// assistant text may be suppressed when send already delivered the message.
	KeyScheduleFireTurn contextKey = "agentkit.schedule_fire_turn"
	// KeyScheduleStateless marks a schedule-fired turn that must not inherit the
	// delivery conversation's active session history (similar to KeyInSubagent).
	KeyScheduleStateless contextKey = "agentkit.schedule_stateless"
)
View Source
const InboundMetaTag = "meta"

InboundMetaTag is the bracket tag for runner inject prefixes on user messages. Example: [meta sender_id=U111 sender_name="Alice" timestamp="..." timezone="UTC"]

View Source
const (
	// KeyIsAdmin marks whether the current user is configured as an admin.
	KeyIsAdmin contextKey = "agentkit.is_admin"
)
View Source
const MetadataSkipPromptMeta = "skipPromptMeta"

MetadataSkipPromptMeta, when true on MessageEvent.Metadata, tells runner not to prepend the optional [meta ...] inbound prefix for that turn.

Variables

View Source
var ErrCommandForbidden = errors.New("command forbidden")

ErrCommandForbidden means the caller is not allowed to run the command.

View Source
var ErrCommandNotHandled = errors.New("command not handled")

ErrCommandNotHandled means the name is not a registered slash command.

View Source
var ErrOutboundPlatformRequired = errors.New("outbound event requires platformID")

ErrOutboundPlatformRequired is returned when OutboundEvent.PlatformID is empty.

Functions

func FormatToolResult added in v0.1.2

func FormatToolResult(out any) (string, error)

FormatToolResult converts a typed tool handler result into model-visible text.

func IsAdmin added in v0.1.16

func IsAdmin(ctx context.Context) bool

IsAdmin reports whether ctx carries admin privileges for the current user.

func MarshalOutboundData

func MarshalOutboundData(v any) json.RawMessage

Types

type ActiveSessionStore added in v0.1.6

type ActiveSessionStore interface {
	ActiveSession(context.Context, SessionID) (SessionID, error)
	SetActiveSession(context.Context, SessionID, SessionID) error
}

ActiveSessionStore maps stable platform/effective session keys to the logical session currently used for model-visible history. Missing mapping means the key itself is the logical session.

type AfterToolHook

type AfterToolHook interface {
	Hook
	AfterTool(context.Context, *ToolResult) error
}

func OnAfterTool

func OnAfterTool(fn func(context.Context, *ToolResult) error) AfterToolHook

OnAfterTool wraps a function as an AfterToolHook.

type Agent

type Agent interface {
	ID() AgentID
	RunTurn(context.Context, TurnInput) error
}

Agent is the execution unit below Loop. It owns prompt, model, tools, policies and hooks for a single agent identity. Conversation state lives in Session; the agent implementation resolves Session via SessionStore using ctx.Value(KeySessionID). Loop does not inject Session objects or duplicate routing fields in TurnInput.

Steer/follow-up/cancel are owned by Loop per SessionID. Loop seeds ctx.Value(KeySessionControl) before RunTurn; Agent reads it to inject queued steering messages at step boundaries without interrupting in-flight steps.

Agent plugins declare SessionStore in their Deps struct (pluginkit injection).

type AgentBindStore added in v0.1.6

type AgentBindStore interface {
	AgentBind(ctx context.Context, id SessionID) (AgentID, error)
	SetAgentBind(ctx context.Context, id SessionID, agent AgentID) error
}

AgentBindStore reads and writes per-session agent routing beside session logs. Missing bind means Runner falls back to loop.defaultAgent.

type AgentCatalogEntry added in v0.1.5

type AgentCatalogEntry interface {
	AgentCatalogEntry() string
}

AgentCatalogEntry optionally describes a built agent for /agent help output.

type AgentID

type AgentID string

type AppInitializer added in v0.1.18

type AppInitializer interface {
	InitApp(context.Context) error
}

AppInitializer performs one-time setup before the runner serves traffic. Examples: seed workspace directories, copy bundled agents/skills, init git.

Config (bootstrap/shell):

runner.default:
  deps:
    init:
      - bootstrap.shell.default

bootstrap.shell.default:
  use: bootstrap/shell
  config:
    commands:
      - echo "init"
  deps:
    workspace: workspace.default

Distinct from StartStop.Start: InitApp runs once at process start and should be idempotent; Start launches long-running background work.

type Approval

type Approval interface {
	Ask(context.Context, ApprovalRequest) (ApprovalDecision, error)
}

type ApprovalDecision

type ApprovalDecision struct {
	Allowed bool
	Reason  string
}

type ApprovalRequest

type ApprovalRequest struct {
	Reason   string
	ToolCall *ToolCall
}

type AssistantMessageEvent

type AssistantMessageEvent struct {
	Type         AssistantMessageEventType `json:"type"`
	ContentIndex int                       `json:"contentIndex,omitempty"`
	Delta        string                    `json:"delta,omitempty"`
	Content      string                    `json:"content,omitempty"`
	ID           string                    `json:"id,omitempty"`
	ToolName     string                    `json:"toolName,omitempty"`
	ToolCall     *ToolCall                 `json:"toolCall,omitempty"`
	Reason       string                    `json:"reason,omitempty"`
	ErrorMessage string                    `json:"errorMessage,omitempty"`
}

AssistantMessageEvent is the wire-safe Pi RPC delta payload (no cumulative partial).

type AssistantMessageEventType

type AssistantMessageEventType string

AssistantMessageEventType mirrors Pi RPC assistantMessageEvent.type values.

const (
	AssistantEventStart         AssistantMessageEventType = "start"
	AssistantEventTextStart     AssistantMessageEventType = "text_start"
	AssistantEventTextDelta     AssistantMessageEventType = "text_delta"
	AssistantEventTextEnd       AssistantMessageEventType = "text_end"
	AssistantEventThinkingStart AssistantMessageEventType = "thinking_start"
	AssistantEventThinkingDelta AssistantMessageEventType = "thinking_delta"
	AssistantEventThinkingEnd   AssistantMessageEventType = "thinking_end"
	AssistantEventToolCallStart AssistantMessageEventType = "toolcall_start"
	AssistantEventToolCallDelta AssistantMessageEventType = "toolcall_delta"
	AssistantEventToolCallEnd   AssistantMessageEventType = "toolcall_end"
	AssistantEventDone          AssistantMessageEventType = "done"
	AssistantEventError         AssistantMessageEventType = "error"
)
const LLMEventMessage AssistantMessageEventType = "message"

LLMEventMessage carries a finalized (or snapshot) ModelMessage rather than a delta. It is internal to the provider/agent boundary and is never forwarded to platforms, so it has no counterpart in the Pi RPC event set.

type BeforeStep

type BeforeStep struct {
	Messages []ModelMessage
}

BeforeStep is invoked before a model step. Hooks read routing context from ctx.Value(KeySessionID) / ctx.Value(KeyAgentID); hooks that need durable state should depend on SessionStore via pluginkit Deps.

type BeforeStepHook

type BeforeStepHook interface {
	Hook
	BeforeStep(context.Context, *BeforeStep) error
}

func OnBeforeStep

func OnBeforeStep(fn func(context.Context, *BeforeStep) error) BeforeStepHook

OnBeforeStep wraps a function as a BeforeStepHook.

type BeforeToolHook

type BeforeToolHook interface {
	Hook
	BeforeTool(context.Context, *ToolCall) error
}

func OnBeforeTool

func OnBeforeTool(fn func(context.Context, *ToolCall) error) BeforeToolHook

OnBeforeTool wraps a function as a BeforeToolHook.

type BudgetState

type BudgetState struct {
	RemainingSteps         int
	RemainingContinuations int
	RemainingSeconds       int
	RemainingTokens        int
	// SoftExhausted is true once any limited dimension crosses softRatio.
	SoftExhausted bool
	// Exhausted is true when a hard limit is reached; Continue is then ignored.
	Exhausted bool
}

BudgetState reports what is left of the run budget. Unlimited dimensions report -1 so hooks can distinguish "no limit" from "nothing left".

type Command

type Command interface {
	Name() string
	Alias() string
	Description() string
	CommandExec(ctx context.Context, args string) (string, error)
}

Command is one slash command contribution.

type CommandCollector

type CommandCollector interface {
	SetCommands(providers []CommandProvider) error
}

CommandCollector receives CommandProvider contributions after pluginkit build. Runner wires them with build.WireContributions during Run.

type CommandProvider

type CommandProvider interface {
	Commands() []Command
}

CommandProvider contributes human-facing commands from a built plugin instance. It is designed to work with pluginkit/build.WireContributions so commands can live next to the capability that owns their behavior.

type Commands

type Commands interface {
	Dispatch(ctx context.Context, name string, rawArgs string) (string, error)
	List() []Command
}

Commands is a post-build slash command catalog for platforms.

type ContentPart

type ContentPart struct {
	Type   string `json:"type"`
	Text   string `json:"text,omitempty"`
	URL    string `json:"url,omitempty"`
	MIME   string `json:"mime,omitempty"`
	Detail string `json:"detail,omitempty"`
	// Source is a workspace-relative attachment path (e.g. upload/foo.png) used
	// for session persistence and vision replay; not sent to model providers.
	// Persisted attachments use type attachment_ref (see cap/media).
	Source string `json:"source,omitempty"`
}

type Decision

type Decision struct {
	Kind   DecisionKind
	Reason string
	// Audit is merged into ToolResult.Audit when the tool runtime denies a call.
	Audit map[string]string
}

func Allow

func Allow() Decision

func Ask

func Ask(reason string) Decision

func Deny

func Deny(reason string) Decision

type DecisionKind

type DecisionKind string
const (
	DecisionAllow DecisionKind = "allow"
	DecisionDeny  DecisionKind = "deny"
	DecisionAsk   DecisionKind = "ask"
)

type EventID

type EventID string

type EventSeq

type EventSeq int64

type EventType

type EventType string
const (
	EventTurnStart               EventType = "turn/start"
	EventTurnEnd                 EventType = "turn/end"
	EventStepStart               EventType = "step/start"
	EventStepEnd                 EventType = "step/end"
	EventUserMessage             EventType = "user/message"
	EventMessageStart            EventType = "message/start"
	EventMessageUpdate           EventType = "message/update"
	EventMessageEnd              EventType = "message/end"
	EventAssistantMessage        EventType = "assistant/message"
	EventToolCall                EventType = "tool/call"
	EventToolResult              EventType = "tool/result"
	EventCompaction              EventType = "session/compaction"
	EventSkillLoad               EventType = "skill/load"
	EventAutoRetryStart          EventType = "retry/start"
	EventAutoRetryEnd            EventType = "retry/end"
	EventSummarizationRetryStart EventType = "summarization/retry/start"
	EventSummarizationRetryEnd   EventType = "summarization/retry/end"
	EventOverflowRecovery        EventType = "overflow/recovery"
	EventTurnContinue            EventType = "turn/continue"
	EventUsage                   EventType = "usage"
	EventTodoUpdate              EventType = "todo/update"
	EventRunFinish               EventType = "run/finish"
	EventSessionRecovery         EventType = "session/recovery"
	EventSubagentStart           EventType = "subagent/start"
	EventSubagentEnd             EventType = "subagent/end"
	EventPermissionRequest       EventType = "permission/request"
	EventPermissionResolved      EventType = "permission/resolved"
)

type FollowUpMode

type FollowUpMode string

FollowUpMode controls how queued follow-up messages are drained after a turn.

const (
	FollowUpOneAtATime FollowUpMode = "one-at-a-time"
	FollowUpAll        FollowUpMode = "all"
)

type Hook

type Hook interface {
	// contains filtered or unexported methods
}

Hook marks a value that may implement one or more hook point interfaces. Execution order follows deps.providers list order; within a provider, Hooks() slice order is preserved.

type HookProvider

type HookProvider interface {
	Hooks() []Hook
}

type HookRuntime

type HookRuntime interface {
	BeforeStep(context.Context, *BeforeStep) error
	BeforeTool(context.Context, *ToolCall) error
	AfterTool(context.Context, *ToolResult) error
	TurnStopping(context.Context, *TurnStopping) error
}

type JSONSchema

type JSONSchema struct {
	Type        string                `json:"type,omitempty"`
	Description string                `json:"description,omitempty"`
	Properties  map[string]JSONSchema `json:"properties,omitempty"`
	Required    []string              `json:"required,omitempty"`
	Items       *JSONSchema           `json:"items,omitempty"`
	Raw         map[string]any        `json:"-"`
}

func (JSONSchema) MarshalJSON

func (s JSONSchema) MarshalJSON() ([]byte, error)

MarshalJSON emits Raw when set so MCP and other external schemas round-trip intact.

func (*JSONSchema) UnmarshalJSON

func (s *JSONSchema) UnmarshalJSON(data []byte) error

UnmarshalJSON stores the full object in Raw and also fills known fields when present.

type LLMEvent

type LLMEvent struct {
	Type         AssistantMessageEventType
	Message      *ModelMessage
	ContentIndex int
	Delta        string
	ToolCall     *ToolCall
	Usage        *Usage
}

LLMEvent reuses AssistantMessageEventType so provider output and the platform-facing stream share one vocabulary. Providers emit the text_*, thinking_* and toolcall_* values plus LLMEventMessage; AssistantEventDone and AssistantEventError are wire-only and never produced here.

type LLMProvider

type LLMProvider interface {
	Name() string
	Stream(context.Context, LLMRequest) (LLMStream, error)
}

LLMProvider streams model responses for an already assembled request. LLMRequest carries model-visible messages only; session routing is handled by the agent runtime before and after the provider boundary.

type LLMRequest

type LLMRequest struct {
	Model    string
	Messages []ModelMessage
	Tools    []ToolSpec
}

type LLMStream

type LLMStream interface {
	Recv() (LLMEvent, error)
	Close() error
}

type Loop

type Loop interface {
	Dispatch(context.Context, LoopRequest) error
	Steer(context.Context, ModelMessage) error
	FollowUp(context.Context, ModelMessage) error
	// IsSessionBusy reports whether a turn is currently executing for the session.
	IsSessionBusy(SessionID) bool
	// TryDeliverPermission consumes a typed permission reply. It returns true
	// when the message was handled and must not start a new turn.
	TryDeliverPermission(MessageEvent) bool
	// SupersedePendingForInbound cancels an active permission wait when a new
	// user message arrives without a permission reply.
	SupersedePendingForInbound(MessageEvent)
}

Loop is the turn scheduler. It routes inbound MessageEvents to agents, serializes work per SessionID, and owns per-session steer/follow-up control. Loop seeds ctx with KeySessionID/KeyDeliverySessionID/KeyStoreSessionID/ KeyAgentID/KeyPlatformID/KeyUserID/KeyMessageMetadata/KeySessionControl/KeyOutboundEmit before calling the agent. It does not resolve Session objects; that is the agent's responsibility.

type LoopRequest

type LoopRequest struct {
	Event             MessageEvent
	DeliverySessionID SessionID // platform delivery target; empty means Event.SessionID
	StoreSessionID    SessionID // logical history id; empty means Event.SessionID
	Emit              OutboundEmit
	Capability        any // permission.Capability, resolved by runner from the inbound platform
}

LoopRequest wraps one inbound message. Runner rewrites Event.SessionID to the effective id (after sessionScope) before Dispatch; DeliverySessionID keeps the platform routing target for outbound Send.

type MessageEndPayload

type MessageEndPayload struct {
	Message ModelMessage `json:"message"`
}

MessageEndPayload is emitted when an assistant message is finalized.

type MessageEvent

type MessageEvent struct {
	SessionID         SessionID // required: loop/history id
	DeliverySessionID SessionID // optional: platform delivery override
	AgentID           AgentID
	PlatformID        string
	UserID            string
	Message           ModelMessage
	// Metadata is optional platform context copied onto persisted user messages.
	Metadata map[string]any `json:"metadata,omitempty"`
	// Reply carries a permission answer as JSON. Decode with permission.DecodeReply.
	Reply json.RawMessage `json:"reply,omitempty"`
}

MessageEvent is the inbound envelope from Platform to Loop. SessionID is the conversation key for loop locking and history. When DeliverySessionID is set, outbound routing uses it instead of SessionID (schedule side sessions).

type MessageStartPayload

type MessageStartPayload struct {
	Message ModelMessage `json:"message"`
}

MessageStartPayload is emitted when assistant streaming begins.

type MessageUpdatePayload

type MessageUpdatePayload struct {
	Usage                 *Usage                `json:"usage,omitempty"`
	AssistantMessageEvent AssistantMessageEvent `json:"assistantMessageEvent"`
}

MessageUpdatePayload matches Pi RPC message_update events.

type ModelMessage

type ModelMessage struct {
	Role        string
	Content     []ContentPart
	ToolCalls   []ToolCall
	ToolResults []ToolResult
}

ModelMessage is model-visible content only. Session routing lives on event envelopes and context keys, not on ModelMessage.

type OutboundEmit

type OutboundEmit func(context.Context, OutboundEvent) error

OutboundEmit sends platform events during a turn. When nil, streaming is suppressed.

type OutboundEvent

type OutboundEvent struct {
	SessionID  SessionID // required
	AgentID    AgentID
	PlatformID string // required
	UserID     string
	Type       EventType
	Data       json.RawMessage
}

OutboundEvent is the outbound envelope from Agent/Loop to Platform. SessionID must match the conversation that produced the turn so the platform can route the reply to the correct IM target. PlatformID is required; multiplex rejects empty values instead of broadcasting to every leaf platform.

func (OutboundEvent) RequirePlatformID added in v0.1.10

func (e OutboundEvent) RequirePlatformID() error

RequirePlatformID reports whether the event names a target platform.

type Platform

type Platform interface {
	Receive(context.Context) (MessageEvent, error)
	Send(context.Context, OutboundEvent) error
}

Platform adapts external transports into AgentKit message events. Every inbound MessageEvent must carry a delivery SessionID (finest grain); runner applies sessionScope for scheduling and history. OutboundEvent.SessionID must be the delivery id so replies reach the correct IM target.

Concurrency contract:

  • Receive is called from a single goroutine and may block.
  • Send must be safe for concurrent use. Runner runs turns from different effective sessions in parallel (runner.config.maxConcurrentTurns, default 64); each turn emits from its own goroutine.

type PlatformIdentifier added in v0.1.1

type PlatformIdentifier interface {
	PlatformID() string
}

PlatformIdentifier exposes the stable routing ID for a leaf platform. Multiplex and other aggregators use it to key sub-platforms without extra config; conflicts are disambiguated with an index suffix.

type Policy

type Policy interface {
	Evaluate(context.Context, PolicyInput) (Decision, error)
}

func PolicyFunc

func PolicyFunc(fn func(context.Context, PolicyInput) Decision) Policy

PolicyFunc wraps a function as a Policy implementation.

type PolicyInput

type PolicyInput struct {
	ToolCall *ToolCall
}

PolicyInput carries the policy payload. Per-conversation policy should read ctx.Value(KeySessionID) / ctx.Value(KeyAgentID) / ctx.Value(KeyUserID).

type PromptAssembler

type PromptAssembler interface {
	Assemble(context.Context, PromptRequest) ([]ModelMessage, error)
}

PromptAssembler builds the model request prompt from registered sections.

type PromptRequest

type PromptRequest struct {
	Messages []ModelMessage
}

PromptRequest carries model-visible prompt inputs. Routing context is read from ctx.Value(KeySessionID) / ctx.Value(KeyAgentID) / ctx.Value(KeyUserID), not duplicated here.

type PromptSection

type PromptSection struct {
	Name    string
	Content string
}

PromptSection is one section provider contribution before it is folded into the leading system message.

type Runner

type Runner interface {
	// Run starts the process. result is the build graph produced alongside this
	// runner; implementations wire CommandProvider contributions to
	// commands/registry and run AppInitializer hooks before serving traffic.
	Run(context.Context, *build.Result) error
	Stop(context.Context) error
}

Runner is the root plugin type. It owns process lifecycle and connects a Platform to a Loop.

type Section

type Section struct {
	Name  string
	Build func(context.Context, PromptRequest) (PromptSection, error)
}

type SectionProvider

type SectionProvider interface {
	Sections() []Section
}

type Session

type Session interface {
	ID() SessionID
	Append(context.Context, SessionEvent) (EventSeq, error)
	Read(context.Context, EventSeq) ([]SessionEvent, error)
	DeriveMessages(context.Context) ([]ModelMessage, error)
}

Session is the durable source of truth for model-visible state.

type SessionEvent

type SessionEvent struct {
	ID        EventID
	Seq       EventSeq
	SessionID SessionID
	AgentID   AgentID
	Type      EventType
	Data      json.RawMessage
	CreatedAt time.Time
	// UserID attributes the event to an end user. It is set on user messages
	// when the platform knows who spoke, and is what makes a session shared by a
	// whole Slack channel legible: without it the model reads one undifferentiated
	// stream of user turns. Empty for single-user transports such as the CLI, and
	// for everything the agent itself produces.
	UserID string
	// Metadata carries optional platform fields for replay-time user-message
	// templates (display name, channel label, etc.). Persisted on user messages.
	Metadata map[string]any `json:"metadata,omitempty"`
}

type SessionID

type SessionID string

SessionID identifies a conversation unit. Platforms emit a delivery SessionID (finest grain: channel + optional :t:thread + optional :u:user). Runner applies sessionScope to derive the effective SessionID used for Loop locking and session history; outbound replies still use the delivery id.

Delivery examples:

slack:C123ABC
slack:C123ABC:t:1712345678.123456:u:U456
feishu:oc_xxx:om_yyy
cli:default

Loop treats the effective SessionID as opaque. Agents read and append history using the logical store SessionID resolved for the turn. Only platform plugins decode delivery SessionIDs into IM routing targets.

type SessionStore

type SessionStore interface {
	Get(context.Context, SessionID) (Session, error)
}

SessionStore resolves durable sessions by opaque SessionID. Platform plugins generate SessionID values (cc-connect style: platform:segment:...); agent plugins depend on SessionStore and call Get with the logical history id resolved for the turn. Loop only uses SessionID for routing and per-session locking, never SessionStore.Get.

type SlashAdminContext added in v0.1.16

type SlashAdminContext interface {
	EnrichSlashContext(ctx context.Context) context.Context
}

SlashAdminContext enriches slash command ctx (for example KeyIsAdmin).

type Tool

type Tool interface {
	Name() string
	Description() string
	InputSchema() JSONSchema
	Call(context.Context, json.RawMessage) (string, error)
}

Tool is the model-visible consumer plugin type.

Call receives only the raw arguments and returns model-visible text. The tool runtime stamps call identity onto a ToolResult before writing to session or sending back to the model.

func First

func First(pack ToolPack) Tool

First returns the only tool in a single-tool pack.

type ToolBuilder

type ToolBuilder[In, Out any] struct {
	// contains filtered or unexported fields
}

func NewTool

func NewTool[In, Out any](name string, handler func(context.Context, In) (Out, error)) *ToolBuilder[In, Out]

NewTool starts building a typed tool plugin. The input schema is inferred from In; any inference error surfaces from Build. Handler output is adapted to model-visible text: string is returned as-is, other values are JSON.

func (*ToolBuilder[In, Out]) Build

func (b *ToolBuilder[In, Out]) Build() (Tool, error)

func (*ToolBuilder[In, Out]) Description

func (b *ToolBuilder[In, Out]) Description(desc string) *ToolBuilder[In, Out]

func (*ToolBuilder[In, Out]) Schema

func (b *ToolBuilder[In, Out]) Schema(schema JSONSchema) *ToolBuilder[In, Out]

Schema replaces the inferred input schema, discarding any inference error.

type ToolCall

type ToolCall struct {
	ID    ToolCallID
	Name  string
	Input json.RawMessage
}

type ToolCallID

type ToolCallID string

type ToolPack

type ToolPack []Tool

ToolPack is one plugin instance that exposes one or more model-visible tools.

func Pack

func Pack(tools ...Tool) ToolPack

Pack returns a ToolPack from individual tools.

type ToolProvider

type ToolProvider interface {
	ListTools(context.Context) ([]Tool, error)
}

ToolProvider supplies tools whose definitions may change between turns. Implementations re-read configuration or rediscover remote tools on each call.

type ToolResult

type ToolResult struct {
	ID      ToolCallID
	Name    string
	Content string
	Audit   map[string]string
}

ToolResult is a tool output bound to a specific call, used by session history, hooks, and LLM wire-up. Audit is populated by the tool runtime (policy deny, timeout, etc.), not by Tool.Call.

func ResultFromCall added in v0.1.2

func ResultFromCall(call ToolCall, output string) ToolResult

ResultFromCall attaches call identity to a tool output.

type ToolRuntime

type ToolRuntime interface {
	Visible(context.Context) ([]ToolSpec, error)
	Execute(context.Context, ToolCall) (ToolResult, error)
}

type ToolSpec

type ToolSpec struct {
	Name        string
	Description string
	InputSchema JSONSchema
}

type TurnInput

type TurnInput struct {
	Message ModelMessage
	Emit    OutboundEmit
}

TurnInput carries one turn's payload. Routing context is available through ctx.Value(KeySessionID), ctx.Value(KeyAgentID), ctx.Value(KeyUserID), and related agentkit keys.

type TurnStopReason

type TurnStopReason string

TurnStopReason explains why the agent reached the end of a turn segment.

const (
	// StopNoToolCalls means the assistant answered without requesting tools.
	StopNoToolCalls TurnStopReason = "no-tool-calls"
	// StopStepLimit means the per-segment step allowance ran out.
	StopStepLimit TurnStopReason = "step-limit"
	// StopBudget means a hard run budget is exhausted; Continue is ignored.
	StopBudget TurnStopReason = "budget"
)

type TurnStopping

type TurnStopping struct {
	Reason   TurnStopReason
	Steps    int
	Segments int
	Budget   BudgetState
	// Messages is the derived history at the stopping point. Read-only for hooks.
	Messages []ModelMessage
	// Continue holds messages that extend the turn. Hooks append to it.
	Continue   []ModelMessage
	Stop       bool
	StopReason string
}

TurnStopping is invoked when the agent is about to end a turn. Hooks may append Continue messages to extend the turn with another segment, or set Stop to force the turn to end. Stop wins over Continue, and the agent ignores Continue when Budget.Exhausted is true: no hook can outrun a hard budget.

type TurnStoppingHook

type TurnStoppingHook interface {
	Hook
	TurnStopping(context.Context, *TurnStopping) error
}

func OnTurnStopping

func OnTurnStopping(fn func(context.Context, *TurnStopping) error) TurnStoppingHook

OnTurnStopping wraps a function as a TurnStoppingHook.

type Usage

type Usage struct {
	InputTokens  int
	OutputTokens int
	TotalTokens  int
}

type UserTimezoneProvider added in v0.1.12

type UserTimezoneProvider interface {
	UserTimezone(userID string) string
}

UserTimezoneProvider is optional on leaf platforms. Runner uses it when runner.config.inject includes timestamp.

Directories

Path Synopsis
cap
filesystem
Package filesystem holds shared request/result types and gitignore helpers used by file tools.
Package filesystem holds shared request/result types and gitignore helpers used by file tools.
permission
Package permission is the capability boundary for human-in-the-loop decisions routed through the inbound platform (tool approval, ask_user, etc.).
Package permission is the capability boundary for human-in-the-loop decisions routed through the inbound platform (tool approval, ask_user, etc.).
schedule
Package schedule defines the calendar-scheduling capability: a durable set of cron jobs that schedule/cron fires and a tool can edit.
Package schedule defines the calendar-scheduling capability: a durable set of cron jobs that schedule/cron fires and a tool can edit.
subagent
Package subagent defines the delegation capability: running a scoped child agent and bringing back only its conclusion.
Package subagent defines the delegation capability: running a scoped child agent and bringing back only its conclusion.
telemetry
Package telemetry defines the observability exporter boundary for AgentKit.
Package telemetry defines the observability exporter boundary for AgentKit.
tenant
Package tenant derives the isolation unit a conversation belongs to.
Package tenant derives the isolation unit a conversation belongs to.
cmd
agent command
runtime
helpdoc
Package helpdoc resolves pluginkit kind documentation for slash commands.
Package helpdoc resolves pluginkit kind documentation for slash commands.
llm
platform/headless
Package headless holds the platforms that run without an interactive terminal: platform/worker for one-shot batches and platform/timer for in-process schedules.
Package headless holds the platforms that run without an interactive terminal: platform/worker for one-shot batches and platform/timer for in-process schedules.
subagent
Package subagent runs child agents in-process.
Package subagent runs child agents in-process.
scripts
gen-imports command
testing
agenttest
Package agenttest provides shared helpers for AgentKit tests.
Package agenttest provides shared helpers for AgentKit tests.
mcptest
Package mcptest builds the stdio MCP test server and returns a tool/mcp provider for smoke tests.
Package mcptest builds the stdio MCP test server and returns a tool/mcp provider for smoke tests.
openapitest
Package openapitest provides a reusable mock HTTP API, fixture workspace materialization, and helpers for OpenAPI dynamic tool smoke tests.
Package openapitest provides a reusable mock HTTP API, fixture workspace materialization, and helpers for OpenAPI dynamic tool smoke tests.
presettest
Package presettest loads real preset graphs and runs scripted Runner smoke flows.
Package presettest loads real preset graphs and runs scripted Runner smoke flows.
smoke
Smoke tests exercise keyless scripted flows end-to-end without preset graphs.
Smoke tests exercise keyless scripted flows end-to-end without preset graphs.

Jump to

Keyboard shortcuts

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