Documentation
¶
Overview ¶
Package promptkit defines the three-layer prompt assembly framework for Flyto Agent. Mirrors the public mechanism Claude Code uses internally: static base + conditional sections + runtime reminders.
promptkit 包定义 Flyto Agent 的三层 prompt 装配框架. mirror Claude Code 内部公开机制: 静态基座 + 条件块 + 运行时 reminder.
Three layers ¶
Layer 1 - BaseSections: always-on sections forming the cache-stable
prefix. The engine places a cache_control marker after the
last base section so prompt cache hits ~90% on subsequent
turns. Examples: role identity, doing-tasks guidance, tone.
始终在的 cache 稳定前缀, engine 在末尾打 cache_control marker
让后续轮次 cache 命中率 ~90%. 例: 角色定位 / 任务流程 / 语气.
Layer 2 - ConditionalSections: evaluated once at session setup (NOT
per-turn). Caller-defined predicates decide which sections
fire. Examples: git status snapshot only when in git repo,
MCP server instructions only when configured.
会话启动时判定一次 (不每轮跑). 调用方定义条件谓词决定哪些段
触发. 例: 检测到 git 仓库才加 git status, 配 MCP 才加 instructions.
Layer 3 - ReminderTriggers: runtime hooks that fire on engine-emitted
events (tool result, file read, mtime drift, permission
wait, token budget low, custom). Rendered text injects into
the messages array (NOT system prompt) preserving cache
stability. Wrapped in <system-reminder> XML tags by
convention.
运行时 hook, 在 engine 发出事件时触发 (tool 返回 / 文件读 /
mtime 漂 / 权限等待 / token budget 紧张 / 自定义). 渲染文本注入
messages 数组 (不进 system prompt) 保持 cache 稳定. 按惯例用
<system-reminder> XML 标签包.
Why three layers ¶
Pre-ADR-0005 Flyto's engine baked Claude Code's coding-assistant content into the engine itself: 8-section system prompt unconditionally injected (~16KB), reminders re-injected per-turn even when SystemPrompt override claimed to bypass them, no prompt cache. This package abstracts the cc mechanism — caller fills content; framework owns assembly + cache boundary placement. First implementation: core/extra/preset-coding/ (Claude Code preset). Second implementation (planned): preset-extraction for logistics quote extraction.
ADR-0005 之前 Flyto 引擎硬塞 Claude Code 内容: 8 段无条件注入 (~16KB) / SystemPrompt 名义 override 仍每轮塞 reminder / 没接 prompt cache. 本包 抽象 cc 机制 — 调用方填内容, framework 负责装配 + cache 边界. 首个实现: core/extra/preset-coding/ (Claude Code preset). 计划第二实现: preset-extraction 给物流报价表抽取场景.
Escape hatch ¶
Callers who find this interface too rigid can bypass entirely via engine.Config.SystemPrompt (literal string sent to LLM, no decoration, no three-layer assembly). Mutually exclusive with cfg.PromptBundle. See ADR-0005 § 2.3 for the design rationale.
嫌接口僵硬的调用方可走 engine.Config.SystemPrompt 字符串路径绕过 (字面 字符串发给 LLM, 零装饰, 不走三层). 与 cfg.PromptBundle 互斥. 设计依据 见 ADR-0005 § 2.3.
See also ¶
- ADR-0005 § 2.3 — interface signatures + escape hatch rationale.
- core/extra/preset-coding/ — first PromptBundle implementation.
- https://www.dbreunig.com/2026/04/04/how-claude-code-builds-a-system-prompt.html — external reference for cc three-layer mechanism.
Index ¶
Constants ¶
This section is empty.
Variables ¶
var ErrNilBundle = errors.New("promptkit: nil PromptBundle")
ErrNilBundle is returned by Build when given a nil PromptBundle.
ErrNilBundle 在 Build 收到 nil PromptBundle 时返回.
Functions ¶
func Build ¶
func Build(b PromptBundle, ctx BuildContext) (string, int, error)
Build assembles a final system prompt string from a PromptBundle.
Build 装配 PromptBundle 成 system prompt 字符串.
Returns: (assembled prompt, cache boundary byte offset, error). cache boundary is the byte offset after Layer-1 (BaseSections) ends — callers pass this to providers that support cache_control marker placement (e.g. Anthropic API ephemeral cache).
返回: (装配后 prompt, cache 边界字节偏移量, error). cache 边界是 Layer-1 (BaseSections) 结束后的字节偏移 — 调用方将其传给支持 cache_control marker 的 provider (例如 Anthropic API ephemeral cache).
Sections separated by "\n\n" (cc convention). If a Section.Render returns empty string the section is skipped (no spacing emitted).
段间用 "\n\n" 分隔 (cc 惯例). Section.Render 返回空串则跳过该段 (不输出空白).
CLEVER: cache boundary tracked by sb.Len() snapshot after last base section. This requires Build to NOT trim trailing whitespace before the snapshot — providers expect the marker at the byte boundary they see in the wire payload, not the visual string boundary.
CLEVER: cache 边界用 sb.Len() snapshot 在最后一个 base section 后捕获. 这要求 Build 在 snapshot 前不修剪尾部空白 — provider 期望 marker 落在 它在 wire payload 中看到的字节边界, 不是视觉字符串边界.
Types ¶
type BuildContext ¶
type BuildContext struct {
// Cwd is the engine's working directory.
// 引擎工作目录.
Cwd string
// ModelFamily is a coarse model classifier (e.g. "claude", "openai",
// "qwen"). Used by sections that adjust phrasing per model family.
// 模型粗分类 (e.g. "claude" / "openai" / "qwen"). 给按模型家族调整措辞的段用.
ModelFamily string
// Language is an optional language hint (e.g. "zh-CN", "en-US").
// Empty = caller didn't specify; section may default.
// 可选语言提示. 空 = 调用方未指定, 段可默认.
Language string
// HasGitRepo: a git repository was detected at or above Cwd.
// 在 Cwd 或祖先目录检测到 git 仓库.
HasGitRepo bool
// HasMCPServer: at least one MCP server is configured.
// 至少配了一个 MCP server.
HasMCPServer bool
// HasFLYTOMD: a FLYTO.md file was found in any of the standard
// search locations (project root, cwd, ~/.flyto/).
// 在标准搜索位置 (项目根 / cwd / ~/.flyto/) 找到 FLYTO.md.
HasFLYTOMD bool
// Custom is for caller-defined predicates. Section implementations
// type-assert keys they recognize.
// 调用方自定义谓词. Section 实现按识别的 key 做类型断言.
Custom map[string]any
}
BuildContext carries session-level invariants supplied to ConditionalSections at session setup.
BuildContext 是 ConditionalSections 会话启动时收到的会话级不变量.
Implementations should treat these as read-only.
实现者应视为只读.
type Context ¶
type Context = BuildContext
Context is supplied to Section.Render at render time. Currently aliased to BuildContext; kept as a separate type so render-time vs setup-time invariants can diverge later without breaking implementations.
Context 是 Section.Render 时收到的不变量. 当前别名 BuildContext, 单独 命名让 render 时 vs 启动时不变量未来可独立演进而不破坏实现.
type Event ¶
type Event struct {
// Type identifies which TriggerEvent fired.
// 触发的事件类型.
Type TriggerEvent
// ToolName is set for EventToolResult / EventFileRead.
// 给 EventToolResult / EventFileRead 用.
ToolName string
// FilePath is set for EventFileRead / EventFileMTimeDrift.
// 给 EventFileRead / EventFileMTimeDrift 用.
FilePath string
// Payload carries event-specific data. Trigger implementations
// type-assert keys they recognize.
// 事件特定数据. Trigger 实现按识别的 key 做类型断言.
Payload map[string]any
// Custom is the discriminator for EventCustom sub-types.
// EventCustom 子类型的区分字符串.
Custom string
}
Event carries trigger-time payload supplied to Trigger.Inject.
Event 是 Trigger.Inject 时收到的 payload.
type PromptBundle ¶
type PromptBundle interface {
// BaseSections returns the cache-stable always-on prefix.
// Engine places cache_control marker after the last base section.
//
// 始终在的 cache 稳定前缀. engine 在末尾打 cache_control marker.
BaseSections() []Section
// ConditionalSections evaluates trigger predicates once at session
// setup (NOT per-turn) and returns sections whose condition matched.
//
// 会话启动时判定一次 (不每轮跑), 返回触发段.
ConditionalSections(ctx BuildContext) []Section
// ReminderTriggers returns runtime reminder hooks. Engine fires them
// on matching events and injects rendered content into messages array.
//
// 运行时 reminder hook. engine 在匹配事件触发, 注入 messages 数组.
ReminderTriggers() []Trigger
}
PromptBundle assembles a system prompt in the three-layer pattern: static base + conditional sections + runtime reminders.
PromptBundle 按三层模式装配 system prompt: 静态基座 + 条件块 + 运行时 reminder.
All three methods are caller-defined. promptkit owns assembly + cache boundary placement; content ownership stays with the implementor.
三个方法都由调用方实现. promptkit 只管装配 + cache 边界, 内容由实现者负责.
See ADR-0005 § 2.3 for the design rationale (mirror cc, no new concepts).
设计依据见 ADR-0005 § 2.3 (mirror cc, 不发明新概念).
type Section ¶
type Section interface {
// Name returns a stable identifier for debugging and cache-key derivation.
// Implementations should return the same value across the process lifetime.
//
// 稳定标识符, 用于调试和 cache key 派生. 实现者应返回进程生命期不变的值.
Name() string
// Render produces the text content. Context carries cwd, model family,
// language, etc. — engine-provided invariants, NOT domain content.
// Empty string return is legal (signals "section produces nothing this time").
//
// 产出文本. Context 带 cwd / model family / language 等 engine 提供的不变量,
// 不带 domain 内容. 返回空串合法 (表示"本次此段不产出").
Render(ctx Context) (string, error)
}
Section is a renderable prompt fragment.
Section 是可渲染的提示词片段.
type SectionRenderError ¶
SectionRenderError wraps an error from a Section.Render call. Layer is "base" or "conditional"; Section is the offending section's Name().
SectionRenderError 包装 Section.Render 调用的错误. Layer 是 "base" 或 "conditional"; Section 是出错段的 Name().
func (*SectionRenderError) Error ¶
func (e *SectionRenderError) Error() string
func (*SectionRenderError) Unwrap ¶
func (e *SectionRenderError) Unwrap() error
type Trigger ¶
type Trigger interface {
// On returns the event type this trigger listens for.
//
// 监听的事件类型.
On() TriggerEvent
// Inject decides whether to fire on this specific event instance and
// returns the reminder text if firing. Empty string = skip.
// Returned text is wrapped by the engine in <system-reminder> tags.
//
// 决定是否在此具体事件触发, 返回 reminder 文本; 空串 = 跳过.
// 返回文本由 engine 包 <system-reminder> 标签.
Inject(evt Event) string
}
Trigger declares a runtime event handler that injects a system reminder.
Trigger 是运行时事件处理器, 命中时注入 system reminder.
type TriggerEvent ¶
type TriggerEvent int
TriggerEvent enumerates engine-emitted event types Layer-3 triggers can listen for. EventCustom is reserved for caller-defined extensions.
TriggerEvent 列举 engine 发出的事件类型. EventCustom 给调用方扩展.
const ( // EventToolResult fires after any tool returns. // 任一工具返回后触发. EventToolResult TriggerEvent = iota // EventFileRead fires when a Read-style tool reads a file. // Read 类工具读完文件触发. EventFileRead // EventFileMTimeDrift fires when a previously-read file's mtime // has drifted since last cache snapshot. // 之前读过的文件 mtime 自上次缓存以来漂移时触发. EventFileMTimeDrift // EventPermissionWait fires while a permission request is pending. // 权限请求挂起时触发. EventPermissionWait // EventTokenBudgetLow fires when remaining token budget < 20% of cap. // 剩余 token budget < 上限 20% 时触发. EventTokenBudgetLow // EventTurnStart fires at the start of every turn (use sparingly — // per-turn injection inflates messages array fast). // 每轮开始触发 (慎用, 每轮注入让 messages 快速膨胀). EventTurnStart // EventCustom is reserved for caller-defined events. Use Event.Custom // field to discriminate sub-types. // 给调用方自定义, 用 Event.Custom 字段区分子类型. EventCustom )