Files
Agent-Native/runtime/promptx/promptx.go
weichen b73611939e Add the SKILL.md skills system and the promptx system-prompt assembler
Why: roles had no way to carry operational know-how into executions
(RoleSpec.Skills never reached the backend), and the system prompt was
assembled ad hoc with no environment facts or skill index.

Evidence: go vet ./... clean; go test ./... 12 packages ok. Skills
cases cover directory-authoritative naming, optional frontmatter
description extraction, invalid dir skipping, root priority on name
clash, missing-root tolerance, fail-closed unknown-skill loads, 64 KiB
body / 200-byte description caps, load_skill registration only when
skills exist, and end-to-end adapter wiring (index in system prompt,
declared-skill validation failing closed on ghost skills, workspace
.agent/skills auto-merge). promptx cases pin section order, sorted
index, reasoning-profile sentence, and env defaults.

Impact: new runtime/skills + runtime/promptx packages; native.Options
gains SkillsRoots; adapters/native system prompt now composes
instructions + environment (date/model/workspace/profile) + declared
skills + index + deliverables; declared-but-missing skills fail the
step (configuration error, not silent).

Refs: plan B6; RoleSpec.Skills plumbing lands with the squad batch.
2026-10-01 05:46:41 +00:00

107 lines
3.3 KiB
Go

// Package promptx 组装执行内核的系统提示。
//
// 职责边界(要求与实现分离):人格/角色内容由上层(产品提示词库、角色
// Instructions)提供并经 Parts.Instructions 传入;本包只做结构化装配——
// 环境事实、技能索引、交付物与档位约束、护栏段。工具清单仍由 runtime
// 循环在系统提示之后追加,不在本包。
package promptx
import (
"sort"
"strings"
"time"
)
// EnvFacts 是注入系统提示的环境事实(由适配层填写,测试可固定)。
type EnvFacts struct {
Date string // 如 2026-10-01(周一)
Model string
WorkspaceRoot string
Profile string // workspace | reasoning
}
// IndexEntry 是技能索引行。
type IndexEntry struct {
Name string
Description string
}
// Parts 是一次执行的装配输入。
type Parts struct {
Instructions string
// SkillsDeclared 是本次执行显式要求的技能名(角色声明)。
SkillsDeclared []string
// SkillsIndex 是可用技能索引(渐进披露:只列名与描述)。
SkillsIndex []IndexEntry
Deliverables []string
Env EnvFacts
}
// Compose 产出系统提示正文。
func Compose(p Parts) string {
var b strings.Builder
if strings.TrimSpace(p.Instructions) != "" {
b.WriteString(strings.TrimSpace(p.Instructions))
b.WriteString("\n\n")
}
// 环境事实块。
env := p.Env
if env.Date == "" && env.Model == "" && env.WorkspaceRoot == "" {
env.Date = time.Now().Format("2006-01-02")
}
var envLines []string
if env.Date != "" {
envLines = append(envLines, "date: "+env.Date)
}
if env.Model != "" {
envLines = append(envLines, "model: "+env.Model)
}
if env.WorkspaceRoot != "" {
envLines = append(envLines, "workspace: "+env.WorkspaceRoot)
}
if env.Profile != "" {
envLines = append(envLines, "tool profile: "+env.Profile)
}
if len(envLines) > 0 {
b.WriteString("## Environment\n")
b.WriteString(strings.Join(envLines, "\n"))
b.WriteString("\n\n")
}
// 技能块:声明优先 + 全量索引 + 按需加载指引。
if len(p.SkillsDeclared) > 0 || len(p.SkillsIndex) > 0 {
b.WriteString("## Skills\n")
if len(p.SkillsDeclared) > 0 {
b.WriteString("Declared for this task (load with load_skill before relying on them): ")
b.WriteString(strings.Join(p.SkillsDeclared, ", "))
b.WriteString("\n")
}
if len(p.SkillsIndex) > 0 {
entries := make([]IndexEntry, len(p.SkillsIndex))
copy(entries, p.SkillsIndex)
sort.Slice(entries, func(i, j int) bool { return entries[i].Name < entries[j].Name })
b.WriteString("Available skills (name — description; use load_skill for full instructions):\n")
for _, e := range entries {
line := "- " + e.Name
if e.Description != "" {
line += " — " + e.Description
}
b.WriteString(line + "\n")
}
}
b.WriteString("\n")
}
// 交付物与档位约束(保持既有语义原文,行为兼容)。
b.WriteString("Deliverable files: ")
b.WriteString(strings.Join(p.Deliverables, ", "))
b.WriteString(".")
if p.Env.Profile == "reasoning" {
b.WriteString(" Reply with the deliverable text only. Tools are disabled.")
} else {
b.WriteString(" Use workspace tools to write those files. Do not invent paths outside the workspace.")
}
return strings.TrimSpace(b.String())
}