Add system prompt; delete BeadState
BeadState duplicated what execute_code already provides via tool scripts. Removed it entirely. Added SystemPrompt field to agent.Config, prepended as a system message each loop iteration. Built-in prompt covers execute_code usage, sandbox behavior, and skill discovery. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
06ccc80da6
commit
07a212884a
109
agent/bead.go
109
agent/bead.go
|
|
@ -1,109 +0,0 @@
|
|||
package agent
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"ollie/backend"
|
||||
)
|
||||
|
||||
// BeadRef identifies a bead by its mount and ID.
|
||||
type BeadRef struct {
|
||||
Mount string // e.g. "pcloudcc-lneely"
|
||||
ID string // e.g. "bd-abc"
|
||||
}
|
||||
|
||||
// beadJSON is the wire format returned by read_bead.sh --property json.
|
||||
type beadJSON struct {
|
||||
ID string `json:"id"`
|
||||
Title string `json:"title"`
|
||||
Description string `json:"description"`
|
||||
Status string `json:"status"`
|
||||
Assignee string `json:"assignee"`
|
||||
Priority int `json:"priority"`
|
||||
Blockers []string `json:"blockers,omitempty"`
|
||||
Labels []string `json:"labels,omitempty"`
|
||||
CloseReason string `json:"close_reason,omitempty"`
|
||||
}
|
||||
|
||||
// ScriptRunner executes a named tool script with the given arguments,
|
||||
// returning its stdout. Implemented by the caller using ollie/exec.
|
||||
type ScriptRunner func(tool string, args []string) (string, error)
|
||||
|
||||
// BeadState is a bead-backed State implementation.
|
||||
// The bead provides the goal (title + description) and tracks status.
|
||||
// Conversation history is kept in-memory for the duration of the session.
|
||||
type BeadState struct {
|
||||
ref BeadRef
|
||||
run ScriptRunner
|
||||
bead beadJSON
|
||||
history []backend.Message
|
||||
complete bool
|
||||
}
|
||||
|
||||
// LoadBead reads a bead from 9beads and returns a BeadState ready for use.
|
||||
// It claims the bead before returning.
|
||||
func LoadBead(ref BeadRef, run ScriptRunner) (*BeadState, error) {
|
||||
out, err := run("read_bead.sh", []string{
|
||||
"--mount", ref.Mount,
|
||||
"--id", ref.ID,
|
||||
"--property", "json",
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read bead %s/%s: %w", ref.Mount, ref.ID, err)
|
||||
}
|
||||
|
||||
var b beadJSON
|
||||
if err := json.Unmarshal([]byte(strings.TrimSpace(out)), &b); err != nil {
|
||||
return nil, fmt.Errorf("parse bead JSON: %w", err)
|
||||
}
|
||||
|
||||
if _, err := run("claim_bead.sh", []string{
|
||||
"--mount", ref.Mount,
|
||||
"--id", ref.ID,
|
||||
}); err != nil {
|
||||
return nil, fmt.Errorf("claim bead %s/%s: %w", ref.Mount, ref.ID, err)
|
||||
}
|
||||
|
||||
goal := b.Title
|
||||
if b.Description != "" {
|
||||
goal = b.Title + "\n\n" + b.Description
|
||||
}
|
||||
|
||||
return &BeadState{
|
||||
ref: ref,
|
||||
run: run,
|
||||
bead: b,
|
||||
history: []backend.Message{
|
||||
{Role: "user", Content: goal},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *BeadState) Goal() string { return s.history[0].Content }
|
||||
func (s *BeadState) History() []backend.Message { return s.history }
|
||||
func (s *BeadState) IsComplete() bool { return s.complete }
|
||||
|
||||
func (s *BeadState) Update(assistant backend.Message, results []ToolResult) error {
|
||||
s.history = append(s.history, assistant)
|
||||
for _, r := range results {
|
||||
s.history = append(s.history, backend.Message{
|
||||
Role: "tool",
|
||||
Content: r.Content,
|
||||
ToolCallID: r.ToolCallID,
|
||||
})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *BeadState) MarkComplete() error {
|
||||
if _, err := s.run("complete_bead.sh", []string{
|
||||
"--mount", s.ref.Mount,
|
||||
"--id", s.ref.ID,
|
||||
}); err != nil {
|
||||
return fmt.Errorf("complete bead %s/%s: %w", s.ref.Mount, s.ref.ID, err)
|
||||
}
|
||||
s.complete = true
|
||||
return nil
|
||||
}
|
||||
|
|
@ -22,12 +22,13 @@ type OutputMsg struct {
|
|||
|
||||
// Config holds everything the loop needs to run.
|
||||
type Config struct {
|
||||
Backend backend.Backend
|
||||
Model string
|
||||
Tools []backend.Tool
|
||||
Exec ToolExecutor
|
||||
MaxSteps int // 0 → default 1 for sessions, caller sets higher for beads
|
||||
Output func(msg OutputMsg) // nil → discard
|
||||
Backend backend.Backend
|
||||
Model string
|
||||
Tools []backend.Tool
|
||||
Exec ToolExecutor
|
||||
MaxSteps int // 0 → default 1
|
||||
Output func(msg OutputMsg) // nil → discard
|
||||
SystemPrompt string // prepended as system message if non-empty
|
||||
}
|
||||
|
||||
// Loop implements observe → decide → act → update → terminate.
|
||||
|
|
@ -52,7 +53,11 @@ func (l *Loop) Run(ctx context.Context, state State) error {
|
|||
// 1. Observe: history is the context; already up to date in state.
|
||||
|
||||
// 2. Decide: call the model.
|
||||
resp, err := l.cfg.Backend.Chat(ctx, l.cfg.Model, state.History(), l.cfg.Tools)
|
||||
history := state.History()
|
||||
if l.cfg.SystemPrompt != "" {
|
||||
history = append([]backend.Message{{Role: "system", Content: l.cfg.SystemPrompt}}, history...)
|
||||
}
|
||||
resp, err := l.cfg.Backend.Chat(ctx, l.cfg.Model, history, l.cfg.Tools)
|
||||
if err != nil {
|
||||
return fmt.Errorf("step %d decide: %w", step, err)
|
||||
}
|
||||
|
|
|
|||
22
main.go
22
main.go
|
|
@ -20,6 +20,21 @@ import (
|
|||
execpkg "ollie/exec"
|
||||
)
|
||||
|
||||
const systemPrompt = `You are an autonomous agent. You have one tool: execute_code.
|
||||
|
||||
execute_code runs shell code in a sandboxed environment. Three invocation modes:
|
||||
- code: inline bash (default language)
|
||||
- tool + args: run a named tool script
|
||||
- pipe: a sequence of {tool, args} steps run in order
|
||||
|
||||
The sandbox restricts certain operations by policy. A permission error does not
|
||||
necessarily mean a file lacks execute permission — it may be a sandbox boundary.
|
||||
If you hit one, try a different approach or tool rather than retrying the same call.
|
||||
|
||||
Skills (domain-specific scripts and knowledge) are discoverable and loadable
|
||||
through execute_code itself: use discover_skill.sh to find them, load_skill.sh
|
||||
to activate them.`
|
||||
|
||||
// executeCodeTool is the single built-in tool exposed to the model.
|
||||
var executeCodeTool = backend.Tool{
|
||||
Name: "execute_code",
|
||||
|
|
@ -82,9 +97,10 @@ func main() {
|
|||
)
|
||||
|
||||
loopcfg := agent.Config{
|
||||
Backend: be,
|
||||
Model: modelName,
|
||||
Tools: []backend.Tool{executeCodeTool},
|
||||
Backend: be,
|
||||
Model: modelName,
|
||||
SystemPrompt: systemPrompt,
|
||||
Tools: []backend.Tool{executeCodeTool},
|
||||
Exec: func(name string, args json.RawMessage) (string, error) {
|
||||
if name == "execute_code" {
|
||||
return dispatchBuiltinExec(builtinExec, args)
|
||||
|
|
|
|||
Reference in New Issue