refactor: extract context.go fork additions into separate files
Split ContextBuilder fork-specific code to reduce upstream merge conflicts: - context_orch.go: orchestrationGuidance constant (~115 lines) - context_plan.go: 15 plan passthrough methods - context_ext.go: fork-specific fields (contextBuilderExt embedded struct), setter methods, memory accessors, and GetSkillsInfo Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
73f3313cc1
commit
52f3546cbb
4 changed files with 271 additions and 258 deletions
|
|
@ -16,136 +16,17 @@ import (
|
|||
"github.com/sipeed/picoclaw/pkg/logger"
|
||||
"github.com/sipeed/picoclaw/pkg/providers"
|
||||
"github.com/sipeed/picoclaw/pkg/skills"
|
||||
"github.com/sipeed/picoclaw/pkg/tools"
|
||||
"github.com/sipeed/picoclaw/pkg/utils"
|
||||
)
|
||||
|
||||
const orchestrationGuidance = `## Orchestration
|
||||
|
||||
|
||||
|
||||
You are the conductor, not the performer. **Your primary job is to delegate, not to implement.**
|
||||
|
||||
|
||||
|
||||
### spawn (non-blocking) — DEFAULT choice
|
||||
|
||||
Returns immediately. Use for any task that can run independently.
|
||||
|
||||
Call the spawn tool with JSON arguments like this:
|
||||
|
||||
|
||||
|
||||
Tool: spawn
|
||||
|
||||
Arguments: {"task": "Examine pkg/auth/ and report middleware pattern", "preset": "scout", "label": "auth-scout"}
|
||||
|
||||
|
||||
|
||||
Tool: spawn
|
||||
|
||||
Arguments: {"task": "Implement rate limiter in pkg/ratelimit/ with tests", "preset": "coder", "label": "rate-limiter"}
|
||||
|
||||
|
||||
|
||||
### subagent (blocking) — only when you need the answer NOW
|
||||
|
||||
Blocks until the subagent finishes. Use only when you cannot proceed without the result.
|
||||
|
||||
Does not take a preset — it runs with default tools.
|
||||
|
||||
|
||||
|
||||
Tool: subagent
|
||||
|
||||
Arguments: {"task": "Read pkg/config/config.go and list all SubagentsConfig fields", "label": "config-check"}
|
||||
|
||||
|
||||
|
||||
### When to use which
|
||||
|
||||
- spawn: parallel tasks, independent work, implementation, long analysis, >2 tool calls
|
||||
|
||||
- subagent: you need the result before your next decision
|
||||
|
||||
- inline: single quick tool call where delegation overhead is wasteful
|
||||
|
||||
|
||||
|
||||
### Presets (for spawn only)
|
||||
|
||||
| preset | role | can write | can exec |
|
||||
|
||||
|--------|------|-----------|----------|
|
||||
|
||||
| scout | explore, investigate | no | no |
|
||||
|
||||
| analyst | analyze, run tests | no | go test/vet, git |
|
||||
|
||||
| coder | implement + verify | yes (sandbox) | test/lint/fmt |
|
||||
|
||||
| worker | build + install | yes (sandbox) | build/package mgr |
|
||||
|
||||
| coordinator | orchestrate others | yes (sandbox) | general + spawn |
|
||||
|
||||
|
||||
|
||||
### Parallel spawning
|
||||
|
||||
Spawn multiple independent tasks at once — do NOT wait between them:
|
||||
|
||||
|
||||
|
||||
Tool: spawn
|
||||
|
||||
Arguments: {"task": "Analyze error handling patterns in pkg/providers/", "preset": "analyst", "label": "error-patterns"}
|
||||
|
||||
|
||||
|
||||
Tool: spawn
|
||||
|
||||
Arguments: {"task": "List all HTTP endpoints in pkg/miniapp/", "preset": "scout", "label": "endpoints"}
|
||||
|
||||
|
||||
|
||||
After spawning, record the assignment in ## Orchestration > Delegated in MEMORY.md.
|
||||
|
||||
When results come back, synthesize findings and decide the next fork.
|
||||
|
||||
|
||||
|
||||
### Subagent escalation
|
||||
|
||||
Deliberate subagents (coder/worker/coordinator) may ask you questions or submit plans for review.
|
||||
|
||||
When a subagent question appears, respond with the appropriate tool:
|
||||
|
||||
- answer_subagent: Answer a subagent's clarifying question
|
||||
|
||||
- review_subagent_plan: Approve or reject a subagent's execution plan (decision: "approved" or rejection feedback)
|
||||
|
||||
|
||||
|
||||
### Orchestration Memory
|
||||
|
||||
Maintain these sections in MEMORY.md under ## Orchestration:
|
||||
|
||||
- **Delegated**: Active subagent assignments (task ID, preset, description)
|
||||
|
||||
- **Findings**: Synthesized results from completed subagents
|
||||
|
||||
- **Decisions**: Key architectural/implementation decisions made during orchestration`
|
||||
|
||||
type ContextBuilder struct {
|
||||
workspace string
|
||||
workDir string // session-specific working directory (worktree or project subdir)
|
||||
skillsLoader *skills.SkillsLoader
|
||||
memory *MemoryStore
|
||||
tools *tools.ToolRegistry // Direct reference to tool registry
|
||||
peerNote string // set per-call from loop.go for peer session awareness
|
||||
orchestrationEnabled bool // set from AgentLoop when --orchestration flag is used
|
||||
toolDiscoveryBM25 bool
|
||||
toolDiscoveryRegex bool
|
||||
contextBuilderExt // fork-specific fields (see context_ext.go)
|
||||
|
||||
workspace string
|
||||
skillsLoader *skills.SkillsLoader
|
||||
memory *MemoryStore
|
||||
toolDiscoveryBM25 bool
|
||||
toolDiscoveryRegex bool
|
||||
|
||||
// Cache for system prompt to avoid rebuilding on every call.
|
||||
// This fixes issue #607: repeated reprocessing of the entire context.
|
||||
|
|
@ -200,27 +81,6 @@ func NewContextBuilder(workspace string) *ContextBuilder {
|
|||
}
|
||||
}
|
||||
|
||||
// SetToolsRegistry sets the tools registry for dynamic tool summary generation.
|
||||
func (cb *ContextBuilder) SetToolsRegistry(registry *tools.ToolRegistry) {
|
||||
cb.tools = registry
|
||||
}
|
||||
|
||||
// SetWorkDir sets the session-specific working directory (e.g., worktree path
|
||||
// or project subdirectory). Bootstrap files found here take priority over workspace.
|
||||
func (cb *ContextBuilder) SetWorkDir(dir string) {
|
||||
cb.workDir = dir
|
||||
}
|
||||
|
||||
// SetPeerNote sets the peer session awareness note for the current call.
|
||||
func (cb *ContextBuilder) SetPeerNote(note string) {
|
||||
cb.peerNote = note
|
||||
}
|
||||
|
||||
// SetOrchestrationEnabled sets whether orchestration is enabled.
|
||||
func (cb *ContextBuilder) SetOrchestrationEnabled(enabled bool) {
|
||||
cb.orchestrationEnabled = enabled
|
||||
}
|
||||
|
||||
func (cb *ContextBuilder) getIdentity() string {
|
||||
workspacePath, _ := filepath.Abs(filepath.Join(cb.workspace))
|
||||
toolDiscovery := cb.getDiscoveryRule()
|
||||
|
|
@ -1056,114 +916,3 @@ func (cb *ContextBuilder) LoadSkill(name string) (string, bool) {
|
|||
func (cb *ContextBuilder) ListSkills() []skills.SkillInfo {
|
||||
return cb.skillsLoader.ListSkills()
|
||||
}
|
||||
|
||||
// Memory returns the underlying MemoryStore for direct plan queries.
|
||||
func (cb *ContextBuilder) Memory() *MemoryStore {
|
||||
return cb.memory
|
||||
}
|
||||
|
||||
// ---------- Plan passthrough methods ----------
|
||||
|
||||
// ReadMemory reads the long-term memory (MEMORY.md).
|
||||
func (cb *ContextBuilder) ReadMemory() string {
|
||||
return cb.memory.ReadLongTerm()
|
||||
}
|
||||
|
||||
// WriteMemory writes content to the long-term memory file.
|
||||
func (cb *ContextBuilder) WriteMemory(content string) error {
|
||||
return cb.memory.WriteLongTerm(content)
|
||||
}
|
||||
|
||||
// ClearMemory removes the long-term memory file.
|
||||
func (cb *ContextBuilder) ClearMemory() error {
|
||||
return cb.memory.ClearLongTerm()
|
||||
}
|
||||
|
||||
// HasActivePlan returns true if MEMORY.md contains an active plan.
|
||||
func (cb *ContextBuilder) HasActivePlan() bool {
|
||||
return cb.memory.HasActivePlan()
|
||||
}
|
||||
|
||||
// GetPlanStatus returns the plan status: "interviewing", "executing", or "".
|
||||
func (cb *ContextBuilder) GetPlanStatus() string {
|
||||
return cb.memory.GetPlanStatus()
|
||||
}
|
||||
|
||||
// IsPlanComplete returns true if all steps in all phases are [x].
|
||||
func (cb *ContextBuilder) IsPlanComplete() bool {
|
||||
return cb.memory.IsPlanComplete()
|
||||
}
|
||||
|
||||
// IsCurrentPhaseComplete returns true if all steps in the current phase are [x].
|
||||
func (cb *ContextBuilder) IsCurrentPhaseComplete() bool {
|
||||
return cb.memory.IsCurrentPhaseComplete()
|
||||
}
|
||||
|
||||
// AdvancePhase increments the current phase number by 1.
|
||||
func (cb *ContextBuilder) AdvancePhase() error {
|
||||
return cb.memory.AdvancePhase()
|
||||
}
|
||||
|
||||
// SetCurrentPhase sets the current phase number to n.
|
||||
func (cb *ContextBuilder) SetCurrentPhase(n int) error {
|
||||
return cb.memory.SetPhase(n)
|
||||
}
|
||||
|
||||
// GetCurrentPhase returns the current phase number.
|
||||
func (cb *ContextBuilder) GetCurrentPhase() int {
|
||||
return cb.memory.GetCurrentPhase()
|
||||
}
|
||||
|
||||
// GetTotalPhases returns the total number of phases in the plan.
|
||||
func (cb *ContextBuilder) GetTotalPhases() int {
|
||||
return cb.memory.GetTotalPhases()
|
||||
}
|
||||
|
||||
// FormatPlanDisplay returns a user-facing display of the full plan.
|
||||
func (cb *ContextBuilder) FormatPlanDisplay() string {
|
||||
return cb.memory.FormatPlanDisplay()
|
||||
}
|
||||
|
||||
// MarkStep marks a step as done in the specified phase.
|
||||
func (cb *ContextBuilder) MarkStep(phase, step int) error {
|
||||
return cb.memory.MarkStep(phase, step)
|
||||
}
|
||||
|
||||
// AddStep appends a new step to the given phase.
|
||||
func (cb *ContextBuilder) AddStep(phase int, desc string) error {
|
||||
return cb.memory.AddStep(phase, desc)
|
||||
}
|
||||
|
||||
// ValidatePlanStructure validates plan structure for interview->review transition.
|
||||
func (cb *ContextBuilder) ValidatePlanStructure() error {
|
||||
return cb.memory.ValidatePlanStructure()
|
||||
}
|
||||
|
||||
// SetPlanStatus sets the plan status.
|
||||
func (cb *ContextBuilder) SetPlanStatus(status string) error {
|
||||
return cb.memory.SetStatus(status)
|
||||
}
|
||||
|
||||
// GetPlanWorkDir returns the WorkDir from the plan metadata, or "".
|
||||
func (cb *ContextBuilder) GetPlanWorkDir() string {
|
||||
return cb.memory.GetPlanWorkDir()
|
||||
}
|
||||
|
||||
// GetPlanTaskName returns the task description from the plan metadata, or "".
|
||||
func (cb *ContextBuilder) GetPlanTaskName() string {
|
||||
return cb.memory.GetPlanTaskName()
|
||||
}
|
||||
|
||||
// GetSkillsInfo returns information about loaded skills.
|
||||
func (cb *ContextBuilder) GetSkillsInfo() map[string]any {
|
||||
allSkills := cb.skillsLoader.ListSkills()
|
||||
skillNames := make([]string, 0, len(allSkills))
|
||||
for _, s := range allSkills {
|
||||
skillNames = append(skillNames, s.Name)
|
||||
}
|
||||
return map[string]any{
|
||||
"total": len(allSkills),
|
||||
"available": len(allSkills),
|
||||
"names": skillNames,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
68
pkg/agent/context_ext.go
Normal file
68
pkg/agent/context_ext.go
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
package agent
|
||||
|
||||
import "github.com/sipeed/picoclaw/pkg/tools"
|
||||
|
||||
// contextBuilderExt holds fork-specific fields for ContextBuilder.
|
||||
// Embedded in ContextBuilder so existing field access (cb.workDir, cb.tools, etc.) continues to work.
|
||||
// Upstream additions to ContextBuilder won't conflict with these fields.
|
||||
type contextBuilderExt struct {
|
||||
workDir string // session-specific working directory (worktree or project subdir)
|
||||
tools *tools.ToolRegistry // Direct reference to tool registry
|
||||
peerNote string // set per-call from loop.go for peer session awareness
|
||||
orchestrationEnabled bool // set from AgentLoop when --orchestration flag is used
|
||||
}
|
||||
|
||||
// SetToolsRegistry sets the tools registry for dynamic tool summary generation.
|
||||
func (cb *ContextBuilder) SetToolsRegistry(registry *tools.ToolRegistry) {
|
||||
cb.tools = registry
|
||||
}
|
||||
|
||||
// SetWorkDir sets the session-specific working directory (e.g., worktree path
|
||||
// or project subdirectory). Bootstrap files found here take priority over workspace.
|
||||
func (cb *ContextBuilder) SetWorkDir(dir string) {
|
||||
cb.workDir = dir
|
||||
}
|
||||
|
||||
// SetPeerNote sets the peer session awareness note for the current call.
|
||||
func (cb *ContextBuilder) SetPeerNote(note string) {
|
||||
cb.peerNote = note
|
||||
}
|
||||
|
||||
// SetOrchestrationEnabled sets whether orchestration is enabled.
|
||||
func (cb *ContextBuilder) SetOrchestrationEnabled(enabled bool) {
|
||||
cb.orchestrationEnabled = enabled
|
||||
}
|
||||
|
||||
// Memory returns the underlying MemoryStore for direct plan queries.
|
||||
func (cb *ContextBuilder) Memory() *MemoryStore {
|
||||
return cb.memory
|
||||
}
|
||||
|
||||
// ReadMemory reads the long-term memory (MEMORY.md).
|
||||
func (cb *ContextBuilder) ReadMemory() string {
|
||||
return cb.memory.ReadLongTerm()
|
||||
}
|
||||
|
||||
// WriteMemory writes content to the long-term memory file.
|
||||
func (cb *ContextBuilder) WriteMemory(content string) error {
|
||||
return cb.memory.WriteLongTerm(content)
|
||||
}
|
||||
|
||||
// ClearMemory removes the long-term memory file.
|
||||
func (cb *ContextBuilder) ClearMemory() error {
|
||||
return cb.memory.ClearLongTerm()
|
||||
}
|
||||
|
||||
// GetSkillsInfo returns information about loaded skills.
|
||||
func (cb *ContextBuilder) GetSkillsInfo() map[string]any {
|
||||
allSkills := cb.skillsLoader.ListSkills()
|
||||
skillNames := make([]string, 0, len(allSkills))
|
||||
for _, s := range allSkills {
|
||||
skillNames = append(skillNames, s.Name)
|
||||
}
|
||||
return map[string]any{
|
||||
"total": len(allSkills),
|
||||
"available": len(allSkills),
|
||||
"names": skillNames,
|
||||
}
|
||||
}
|
||||
117
pkg/agent/context_orch.go
Normal file
117
pkg/agent/context_orch.go
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
package agent
|
||||
|
||||
const orchestrationGuidance = `## Orchestration
|
||||
|
||||
|
||||
|
||||
You are the conductor, not the performer. **Your primary job is to delegate, not to implement.**
|
||||
|
||||
|
||||
|
||||
### spawn (non-blocking) — DEFAULT choice
|
||||
|
||||
Returns immediately. Use for any task that can run independently.
|
||||
|
||||
Call the spawn tool with JSON arguments like this:
|
||||
|
||||
|
||||
|
||||
Tool: spawn
|
||||
|
||||
Arguments: {"task": "Examine pkg/auth/ and report middleware pattern", "preset": "scout", "label": "auth-scout"}
|
||||
|
||||
|
||||
|
||||
Tool: spawn
|
||||
|
||||
Arguments: {"task": "Implement rate limiter in pkg/ratelimit/ with tests", "preset": "coder", "label": "rate-limiter"}
|
||||
|
||||
|
||||
|
||||
### subagent (blocking) — only when you need the answer NOW
|
||||
|
||||
Blocks until the subagent finishes. Use only when you cannot proceed without the result.
|
||||
|
||||
Does not take a preset — it runs with default tools.
|
||||
|
||||
|
||||
|
||||
Tool: subagent
|
||||
|
||||
Arguments: {"task": "Read pkg/config/config.go and list all SubagentsConfig fields", "label": "config-check"}
|
||||
|
||||
|
||||
|
||||
### When to use which
|
||||
|
||||
- spawn: parallel tasks, independent work, implementation, long analysis, >2 tool calls
|
||||
|
||||
- subagent: you need the result before your next decision
|
||||
|
||||
- inline: single quick tool call where delegation overhead is wasteful
|
||||
|
||||
|
||||
|
||||
### Presets (for spawn only)
|
||||
|
||||
| preset | role | can write | can exec |
|
||||
|
||||
|--------|------|-----------|----------|
|
||||
|
||||
| scout | explore, investigate | no | no |
|
||||
|
||||
| analyst | analyze, run tests | no | go test/vet, git |
|
||||
|
||||
| coder | implement + verify | yes (sandbox) | test/lint/fmt |
|
||||
|
||||
| worker | build + install | yes (sandbox) | build/package mgr |
|
||||
|
||||
| coordinator | orchestrate others | yes (sandbox) | general + spawn |
|
||||
|
||||
|
||||
|
||||
### Parallel spawning
|
||||
|
||||
Spawn multiple independent tasks at once — do NOT wait between them:
|
||||
|
||||
|
||||
|
||||
Tool: spawn
|
||||
|
||||
Arguments: {"task": "Analyze error handling patterns in pkg/providers/", "preset": "analyst", "label": "error-patterns"}
|
||||
|
||||
|
||||
|
||||
Tool: spawn
|
||||
|
||||
Arguments: {"task": "List all HTTP endpoints in pkg/miniapp/", "preset": "scout", "label": "endpoints"}
|
||||
|
||||
|
||||
|
||||
After spawning, record the assignment in ## Orchestration > Delegated in MEMORY.md.
|
||||
|
||||
When results come back, synthesize findings and decide the next fork.
|
||||
|
||||
|
||||
|
||||
### Subagent escalation
|
||||
|
||||
Deliberate subagents (coder/worker/coordinator) may ask you questions or submit plans for review.
|
||||
|
||||
When a subagent question appears, respond with the appropriate tool:
|
||||
|
||||
- answer_subagent: Answer a subagent's clarifying question
|
||||
|
||||
- review_subagent_plan: Approve or reject a subagent's execution plan (decision: "approved" or rejection feedback)
|
||||
|
||||
|
||||
|
||||
### Orchestration Memory
|
||||
|
||||
Maintain these sections in MEMORY.md under ## Orchestration:
|
||||
|
||||
- **Delegated**: Active subagent assignments (task ID, preset, description)
|
||||
|
||||
- **Findings**: Synthesized results from completed subagents
|
||||
|
||||
- **Decisions**: Key architectural/implementation decisions made during orchestration`
|
||||
79
pkg/agent/context_plan.go
Normal file
79
pkg/agent/context_plan.go
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
package agent
|
||||
|
||||
// ---------- Plan passthrough methods ----------
|
||||
// These delegate to MemoryStore and are separated to reduce upstream conflicts.
|
||||
|
||||
// HasActivePlan returns true if MEMORY.md contains an active plan.
|
||||
func (cb *ContextBuilder) HasActivePlan() bool {
|
||||
return cb.memory.HasActivePlan()
|
||||
}
|
||||
|
||||
// GetPlanStatus returns the plan status: "interviewing", "executing", or "".
|
||||
func (cb *ContextBuilder) GetPlanStatus() string {
|
||||
return cb.memory.GetPlanStatus()
|
||||
}
|
||||
|
||||
// IsPlanComplete returns true if all steps in all phases are [x].
|
||||
func (cb *ContextBuilder) IsPlanComplete() bool {
|
||||
return cb.memory.IsPlanComplete()
|
||||
}
|
||||
|
||||
// IsCurrentPhaseComplete returns true if all steps in the current phase are [x].
|
||||
func (cb *ContextBuilder) IsCurrentPhaseComplete() bool {
|
||||
return cb.memory.IsCurrentPhaseComplete()
|
||||
}
|
||||
|
||||
// AdvancePhase increments the current phase number by 1.
|
||||
func (cb *ContextBuilder) AdvancePhase() error {
|
||||
return cb.memory.AdvancePhase()
|
||||
}
|
||||
|
||||
// SetCurrentPhase sets the current phase number to n.
|
||||
func (cb *ContextBuilder) SetCurrentPhase(n int) error {
|
||||
return cb.memory.SetPhase(n)
|
||||
}
|
||||
|
||||
// GetCurrentPhase returns the current phase number.
|
||||
func (cb *ContextBuilder) GetCurrentPhase() int {
|
||||
return cb.memory.GetCurrentPhase()
|
||||
}
|
||||
|
||||
// GetTotalPhases returns the total number of phases in the plan.
|
||||
func (cb *ContextBuilder) GetTotalPhases() int {
|
||||
return cb.memory.GetTotalPhases()
|
||||
}
|
||||
|
||||
// FormatPlanDisplay returns a user-facing display of the full plan.
|
||||
func (cb *ContextBuilder) FormatPlanDisplay() string {
|
||||
return cb.memory.FormatPlanDisplay()
|
||||
}
|
||||
|
||||
// MarkStep marks a step as done in the specified phase.
|
||||
func (cb *ContextBuilder) MarkStep(phase, step int) error {
|
||||
return cb.memory.MarkStep(phase, step)
|
||||
}
|
||||
|
||||
// AddStep appends a new step to the given phase.
|
||||
func (cb *ContextBuilder) AddStep(phase int, desc string) error {
|
||||
return cb.memory.AddStep(phase, desc)
|
||||
}
|
||||
|
||||
// ValidatePlanStructure validates plan structure for interview->review transition.
|
||||
func (cb *ContextBuilder) ValidatePlanStructure() error {
|
||||
return cb.memory.ValidatePlanStructure()
|
||||
}
|
||||
|
||||
// SetPlanStatus sets the plan status.
|
||||
func (cb *ContextBuilder) SetPlanStatus(status string) error {
|
||||
return cb.memory.SetStatus(status)
|
||||
}
|
||||
|
||||
// GetPlanWorkDir returns the WorkDir from the plan metadata, or "".
|
||||
func (cb *ContextBuilder) GetPlanWorkDir() string {
|
||||
return cb.memory.GetPlanWorkDir()
|
||||
}
|
||||
|
||||
// GetPlanTaskName returns the task description from the plan metadata, or "".
|
||||
func (cb *ContextBuilder) GetPlanTaskName() string {
|
||||
return cb.memory.GetPlanTaskName()
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue