execute_code: configurable sandbox, fix dangerous patterns
- Add sandbox parameter to execute_code (default: anvilmcp)
- Load sandbox config from ~/.config/anvillm/sandbox/<name>.yaml
- Fallback to ./cfg/sandbox/<name>.yaml for development
- Add cwd config option: 'temp' for isolated workspace, '{CWD}' for caller's dir
- Create cfg/sandbox/anvilmcp.yaml with restricted defaults
- Update cfg/sandbox/default.yaml with cwd: '{CWD}'
Dangerous pattern fixes (bash, not JS/Python):
- Remove curl/wget block (network controlled by landrun)
- Remove chmod 777, eval(), exec() (wrong syntax)
- Allow /etc/passwd (world-readable, needed for user lookups)
- Fix /dev/ write check to only block /dev/sd* (not /dev/null)
- Fix eval pattern for actual bash usage
Update skills with explicit sandbox specifications:
- anvilmcp sandbox for normal operations
- default sandbox for spawn_agent.sh (needs full permissions)
This commit is contained in:
parent
b51bbf7e72
commit
0d847f96f3
|
|
@ -0,0 +1,36 @@
|
|||
# Default sandbox config for anvilmcp execute_code
|
||||
# Based on hard-coded permissions from execute.go
|
||||
cwd: temp # Use temp workspace (cleaned up after execution)
|
||||
filesystem:
|
||||
ro:
|
||||
- "/etc/alternatives"
|
||||
- "/etc/ld.so.cache"
|
||||
- "/etc/passwd"
|
||||
- "/dev/urandom"
|
||||
rox:
|
||||
- "/usr"
|
||||
- "/lib"
|
||||
- "/lib64"
|
||||
- "/bin"
|
||||
- "{HOME}/go/bin"
|
||||
- "{HOME}/bin"
|
||||
- "{HOME}/.local/bin"
|
||||
- "{HOME}/opt"
|
||||
- "{PLAN9}/bin"
|
||||
rw:
|
||||
- "/dev/null"
|
||||
- "/tmp"
|
||||
- "{NAMESPACE}"
|
||||
rwx:
|
||||
- "{CWD}"
|
||||
env:
|
||||
- NAMESPACE
|
||||
- PATH
|
||||
- HOME
|
||||
- PLAN9
|
||||
- DISPLAY
|
||||
- WAYLAND_DISPLAY
|
||||
- USER
|
||||
- AGENT_ID
|
||||
network:
|
||||
unrestricted: true
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
# Default role: combined most-permissive settings from all tasks/roles
|
||||
# TODO: revisit role-based permissions later
|
||||
cwd: "{CWD}"
|
||||
filesystem:
|
||||
ro:
|
||||
- "{HOME}/.gitconfig"
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -12,6 +12,8 @@ import (
|
|||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
var dangerousPatterns = []*regexp.Regexp{
|
||||
|
|
@ -22,16 +24,13 @@ var dangerousPatterns = []*regexp.Regexp{
|
|||
regexp.MustCompile(`rm\s+(-[a-z]*r[a-z]*\s+)*-[a-z]*f[a-z]*\s+\.\.?(/|$)`), // rm -rf ./ or rm -rf ../
|
||||
regexp.MustCompile(`rm\s+(-[a-z]*r[a-z]*\s+)*-[a-z]*f[a-z]*\s+~`), // rm -rf ~ (home dir)
|
||||
regexp.MustCompile(`rm\s+(-[a-z]*r[a-z]*\s+)*-[a-z]*f[a-z]*\s+\*`), // rm -rf * (glob expansion)
|
||||
regexp.MustCompile(`:\s*\(\s*\)\s*\{\s*:\s*\|\s*:\s*&`), // fork bomb
|
||||
regexp.MustCompile(`\bmkfs\b`), // filesystem format
|
||||
regexp.MustCompile(`\bdd\b.*\bif=/dev/`), // dd from device
|
||||
regexp.MustCompile(`\bchmod\s+777\b`), // world-writable
|
||||
regexp.MustCompile(`\b(curl|wget)\s+\S*://`), // network fetch (any protocol)
|
||||
regexp.MustCompile(`>\s*/dev/`), // write to device
|
||||
regexp.MustCompile(`\beval\s*\(`), // eval execution
|
||||
regexp.MustCompile(`\bexec\s*\(`), // exec execution
|
||||
regexp.MustCompile(`\b(sudo|su)\b`), // privilege escalation
|
||||
regexp.MustCompile(`/etc/(passwd|shadow|sudoers)`), // sensitive files
|
||||
regexp.MustCompile(`:\s*\(\s*\)\s*\{\s*:\s*\|\s*:\s*&`), // fork bomb
|
||||
regexp.MustCompile(`\bmkfs\b`), // filesystem format
|
||||
regexp.MustCompile(`\bdd\b.*\bif=/dev/`), // dd from device
|
||||
regexp.MustCompile(`>\s*/dev/sd`), // write to block device
|
||||
regexp.MustCompile(`\beval\s+".*\$`), // eval with variable expansion
|
||||
regexp.MustCompile(`\b(sudo|su)\s`), // privilege escalation
|
||||
regexp.MustCompile(`/etc/(shadow|sudoers)`), // sensitive files (not passwd)
|
||||
}
|
||||
|
||||
var whitespacePattern = regexp.MustCompile(`\s+`)
|
||||
|
|
@ -109,7 +108,95 @@ func validateCode(code string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func executeCode(code, language string, timeout int) (string, error) {
|
||||
// SandboxConfig represents the sandbox configuration loaded from YAML
|
||||
type SandboxConfig struct {
|
||||
CWD string `yaml:"cwd"` // "temp" for temp workspace, or path template like "{CWD}"
|
||||
Filesystem struct {
|
||||
RO []string `yaml:"ro"`
|
||||
ROX []string `yaml:"rox"`
|
||||
RW []string `yaml:"rw"`
|
||||
RWX []string `yaml:"rwx"`
|
||||
} `yaml:"filesystem"`
|
||||
Env []string `yaml:"env"`
|
||||
Network struct {
|
||||
Unrestricted bool `yaml:"unrestricted"`
|
||||
} `yaml:"network"`
|
||||
}
|
||||
|
||||
// loadSandboxConfig loads sandbox config from ~/.config/anvillm/sandbox/<name>.yaml
|
||||
// Falls back to ./cfg/sandbox/<name>.yaml for development
|
||||
func loadSandboxConfig(name string) (*SandboxConfig, error) {
|
||||
// Validate name to prevent path traversal
|
||||
if name == "" {
|
||||
name = "anvilmcp"
|
||||
}
|
||||
for _, r := range name {
|
||||
if !((r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') || (r >= '0' && r <= '9') || r == '-' || r == '_') {
|
||||
return nil, fmt.Errorf("invalid sandbox name: %s", name)
|
||||
}
|
||||
}
|
||||
|
||||
homeDir, _ := os.UserHomeDir()
|
||||
cfgPath := filepath.Join(homeDir, ".config", "anvillm", "sandbox", name+".yaml")
|
||||
|
||||
// Fall back to ./cfg/sandbox/ for development
|
||||
if _, err := os.Stat(cfgPath); os.IsNotExist(err) {
|
||||
cfgPath = filepath.Join("cfg", "sandbox", name+".yaml")
|
||||
}
|
||||
|
||||
data, err := os.ReadFile(cfgPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to load sandbox config %s: %v", name, err)
|
||||
}
|
||||
|
||||
var cfg SandboxConfig
|
||||
if err := yaml.Unmarshal(data, &cfg); err != nil {
|
||||
return nil, fmt.Errorf("failed to parse sandbox config: %v", err)
|
||||
}
|
||||
|
||||
return &cfg, nil
|
||||
}
|
||||
|
||||
// expandPath replaces template variables with actual values
|
||||
func expandPath(pattern string) string {
|
||||
s := pattern
|
||||
homeDir, _ := os.UserHomeDir()
|
||||
|
||||
// Generic environment variable expansion: {VAR_NAME}
|
||||
re := regexp.MustCompile(`\{([A-Z_][A-Z0-9_]*)\}`)
|
||||
s = re.ReplaceAllStringFunc(s, func(match string) string {
|
||||
varName := match[1 : len(match)-1]
|
||||
|
||||
switch varName {
|
||||
case "HOME":
|
||||
return homeDir
|
||||
case "PLAN9":
|
||||
if plan9 := os.Getenv("PLAN9"); plan9 != "" {
|
||||
return plan9
|
||||
}
|
||||
return "" // Will cause path to be skipped
|
||||
case "NAMESPACE":
|
||||
if ns := os.Getenv("NAMESPACE"); ns != "" {
|
||||
return ns
|
||||
}
|
||||
if nsCmd, err := exec.Command("namespace").Output(); err == nil {
|
||||
return strings.TrimSpace(string(nsCmd))
|
||||
}
|
||||
return fmt.Sprintf("/tmp/ns.%s.:0", os.Getenv("USER"))
|
||||
case "CWD":
|
||||
return "" // Handled specially - replaced with workDir
|
||||
}
|
||||
|
||||
if val := os.Getenv(varName); val != "" {
|
||||
return val
|
||||
}
|
||||
return "" // Will cause path to be skipped
|
||||
})
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
func executeCode(code, language string, timeout int, sandbox string) (string, error) {
|
||||
start := time.Now()
|
||||
|
||||
if timeout <= 0 {
|
||||
|
|
@ -129,113 +216,92 @@ func executeCode(code, language string, timeout int) (string, error) {
|
|||
return "", err
|
||||
}
|
||||
|
||||
// Load sandbox config
|
||||
cfg, err := loadSandboxConfig(sandbox)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
homeDir, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get home dir: %v", err)
|
||||
}
|
||||
|
||||
// Determine working directory based on config
|
||||
var workDir string
|
||||
var cleanupWorkDir bool
|
||||
|
||||
workspaceBase := filepath.Join(homeDir, ".cache", "anvillm", "exec")
|
||||
if err := os.MkdirAll(workspaceBase, 0700); err != nil {
|
||||
return "", fmt.Errorf("failed to create workspace base: %v", err)
|
||||
if cfg.CWD == "" || cfg.CWD == "temp" {
|
||||
// Use temp workspace (original behavior)
|
||||
workspaceBase := filepath.Join(homeDir, ".cache", "anvillm", "exec")
|
||||
if err := os.MkdirAll(workspaceBase, 0700); err != nil {
|
||||
return "", fmt.Errorf("failed to create workspace base: %v", err)
|
||||
}
|
||||
workDir, err = os.MkdirTemp(workspaceBase, "anvilmcp-*")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to create workspace: %v", err)
|
||||
}
|
||||
cleanupWorkDir = true
|
||||
} else {
|
||||
// Use configured path (e.g., "{CWD}" expands to current directory)
|
||||
workDir = expandPath(cfg.CWD)
|
||||
if workDir == "" {
|
||||
workDir, _ = os.Getwd()
|
||||
}
|
||||
cleanupWorkDir = false
|
||||
}
|
||||
|
||||
workDir, err := os.MkdirTemp(workspaceBase, "anvilmcp-*")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to create workspace: %v", err)
|
||||
if cleanupWorkDir {
|
||||
defer os.RemoveAll(workDir)
|
||||
}
|
||||
defer os.RemoveAll(workDir)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeout)*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// Check for landrun
|
||||
landrunPath, err := exec.LookPath("landrun")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("landrun not found: %v", err)
|
||||
}
|
||||
|
||||
// Get namespace for 9p access
|
||||
namespace := os.Getenv("NAMESPACE")
|
||||
if namespace == "" {
|
||||
// Try to get from namespace command
|
||||
if nsCmd, err := exec.Command("namespace").Output(); err == nil {
|
||||
namespace = strings.TrimSpace(string(nsCmd))
|
||||
}
|
||||
if namespace == "" {
|
||||
namespace = fmt.Sprintf("/tmp/ns.%s.:0", os.Getenv("USER"))
|
||||
}
|
||||
}
|
||||
|
||||
// Get AGENT_ID if set
|
||||
agentID := os.Getenv("AGENT_ID")
|
||||
|
||||
var cmd *exec.Cmd
|
||||
switch language {
|
||||
case "bash", "":
|
||||
args := []string{
|
||||
"--rwx", workDir,
|
||||
"--unrestricted-network",
|
||||
}
|
||||
|
||||
// Add common system paths if they exist
|
||||
systemPaths := []struct {
|
||||
flag string
|
||||
path string
|
||||
}{
|
||||
{"--rox", "/usr"},
|
||||
{"--rox", "/lib"},
|
||||
{"--rox", "/lib64"},
|
||||
{"--rox", "/bin"},
|
||||
{"--ro", "/etc/alternatives"},
|
||||
{"--ro", "/etc/ld.so.cache"},
|
||||
{"--ro", "/etc/passwd"},
|
||||
{"--rw", "/dev/null"},
|
||||
{"--ro", "/dev/urandom"},
|
||||
{"--rw", "/tmp"},
|
||||
}
|
||||
|
||||
for _, sp := range systemPaths {
|
||||
if _, err := os.Stat(sp.path); err == nil {
|
||||
args = append(args, sp.flag, sp.path)
|
||||
args := []string{}
|
||||
|
||||
// Add filesystem permissions from config
|
||||
addPaths := func(flag string, paths []string) {
|
||||
for _, p := range paths {
|
||||
expanded := expandPath(p)
|
||||
if expanded == "" {
|
||||
continue
|
||||
}
|
||||
// Handle {CWD} specially
|
||||
if strings.Contains(p, "{CWD}") {
|
||||
expanded = strings.ReplaceAll(p, "{CWD}", workDir)
|
||||
}
|
||||
if _, err := os.Stat(expanded); err == nil {
|
||||
args = append(args, flag, expanded)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add namespace if set
|
||||
if namespace != "" {
|
||||
args = append(args, "--rw", namespace)
|
||||
|
||||
addPaths("--ro", cfg.Filesystem.RO)
|
||||
addPaths("--rox", cfg.Filesystem.ROX)
|
||||
addPaths("--rw", cfg.Filesystem.RW)
|
||||
addPaths("--rwx", cfg.Filesystem.RWX)
|
||||
|
||||
// Network
|
||||
if cfg.Network.Unrestricted {
|
||||
args = append(args, "--unrestricted-network")
|
||||
}
|
||||
|
||||
// Add common user binary directories
|
||||
userBinDirs := []string{
|
||||
filepath.Join(homeDir, "bin"),
|
||||
filepath.Join(homeDir, ".local", "bin"),
|
||||
filepath.Join(homeDir, "opt"),
|
||||
filepath.Join(homeDir, "go", "bin"),
|
||||
}
|
||||
|
||||
// Add PLAN9 bin if set
|
||||
if plan9 := os.Getenv("PLAN9"); plan9 != "" {
|
||||
userBinDirs = append(userBinDirs, filepath.Join(plan9, "bin"))
|
||||
}
|
||||
|
||||
for _, dir := range userBinDirs {
|
||||
if _, err := os.Stat(dir); err == nil {
|
||||
args = append(args, "--rox", dir)
|
||||
|
||||
// Environment variables
|
||||
for _, env := range cfg.Env {
|
||||
if os.Getenv(env) != "" || env == "HOME" || env == "USER" || env == "PATH" {
|
||||
args = append(args, "--env", env)
|
||||
}
|
||||
}
|
||||
|
||||
args = append(args,
|
||||
"--env", "NAMESPACE",
|
||||
"--env", "PATH",
|
||||
"--env", "HOME",
|
||||
"--env", "PLAN9",
|
||||
"--env", "DISPLAY",
|
||||
"--env", "WAYLAND_DISPLAY",
|
||||
"--env", "USER",
|
||||
)
|
||||
if agentID != "" {
|
||||
args = append(args, "--env", "AGENT_ID")
|
||||
}
|
||||
|
||||
args = append(args, "--", "bash", "-c", code)
|
||||
|
||||
cmd = exec.CommandContext(ctx, landrunPath, args...)
|
||||
|
|
@ -253,7 +319,6 @@ func executeCode(code, language string, timeout int) (string, error) {
|
|||
err = cmd.Run()
|
||||
output := outputBuf.Bytes()
|
||||
|
||||
// Append truncation notice if output was truncated
|
||||
if lw.truncated {
|
||||
output = append(output, []byte("\n[output truncated at 10MB]")...)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -95,6 +95,7 @@ func main() {
|
|||
"code": {Type: "string", Description: "Bash code to execute"},
|
||||
"language": {Type: "string", Description: "Programming language (bash)", Enum: []string{"bash"}},
|
||||
"timeout": {Type: "integer", Description: "Timeout in seconds (default: 30)"},
|
||||
"sandbox": {Type: "string", Description: "Sandbox config name (default: default)"},
|
||||
},
|
||||
Required: []string{"code"},
|
||||
},
|
||||
|
|
@ -132,13 +133,17 @@ func handleToolCall(req MCPRequest) {
|
|||
if t, ok := params.Arguments["timeout"].(float64); ok {
|
||||
timeout = int(t)
|
||||
}
|
||||
sandbox, _ := params.Arguments["sandbox"].(string)
|
||||
if sandbox == "" {
|
||||
sandbox = "anvilmcp"
|
||||
}
|
||||
|
||||
// Acquire execution slot
|
||||
executionSemaphore <- struct{}{}
|
||||
defer func() { <-executionSemaphore }()
|
||||
|
||||
fmt.Fprintf(os.Stderr, "[anvilmcp] Executing bash code (timeout: %ds)\n", timeout)
|
||||
result, err := executeCode(code, language, timeout)
|
||||
fmt.Fprintf(os.Stderr, "[anvilmcp] Executing bash code (timeout: %ds, sandbox: %s)\n", timeout, sandbox)
|
||||
result, err := executeCode(code, language, timeout, sandbox)
|
||||
|
||||
// Log token comparison
|
||||
codeTokens := estimateTokens(code)
|
||||
|
|
|
|||
|
|
@ -9,12 +9,28 @@ when_to_load: Load when you need to send messages to other agents, respond to th
|
|||
|
||||
## Tools
|
||||
|
||||
**IMPORTANT:** All anvillm-communication tools must be run via `execute_code` tool.
|
||||
**IMPORTANT:** Read the $AGENT_ID environment variable to get your agent ID
|
||||
|
||||
- Discover agents: `bash <(9p read agent/tools/agents/list_sessions.sh) | grep "$(pwd)"`
|
||||
- Send message: `bash <(9p read agent/tools/messaging/send_message.sh) <FROM> <TO> <TYPE> <SUBJECT> <BODY>`
|
||||
- Check inbox: `bash <(9p read agent/tools/messaging/read_inbox.sh) <AGENT_ID>`
|
||||
Discover agents:
|
||||
```
|
||||
Tool: execute_code
|
||||
sandbox: anvilmcp
|
||||
code: bash <(9p read agent/tools/agents/list_sessions.sh) | grep "$(pwd)"
|
||||
```
|
||||
|
||||
Send message:
|
||||
```
|
||||
Tool: execute_code
|
||||
sandbox: anvilmcp
|
||||
code: bash <(9p read agent/tools/messaging/send_message.sh) <FROM> <TO> <TYPE> <SUBJECT> <BODY>
|
||||
```
|
||||
|
||||
Check inbox:
|
||||
```
|
||||
Tool: execute_code
|
||||
sandbox: anvilmcp
|
||||
code: bash <(9p read agent/tools/messaging/read_inbox.sh) <AGENT_ID>
|
||||
```
|
||||
|
||||
## Rules
|
||||
|
||||
|
|
|
|||
|
|
@ -10,13 +10,42 @@ Create and control agent sessions.
|
|||
|
||||
## Commands
|
||||
|
||||
All commands must be run via `execute_code` tool, EXCEPT spawn_agent.sh which must NOT use execute_code:
|
||||
All commands use `execute_code` with `sandbox: "anvilmcp"`, EXCEPT spawn_agent.sh which requires `sandbox: "default"`.
|
||||
|
||||
- List sessions: `list_sessions.sh`
|
||||
- Create session: `create_session.sh <backend> <cwd> [sandbox=<sandbox>] [model=<model>]`
|
||||
- Control session: `control_session.sh <session-id> <command>`
|
||||
- Spawn agent: `spawn_agent.sh <agent-id> [cwd-path] [initial-context-prompt]` (do NOT use execute_code)
|
||||
- Kill agent: `kill_agent.sh [cwd-path]`
|
||||
List sessions:
|
||||
```
|
||||
Tool: execute_code
|
||||
sandbox: anvilmcp
|
||||
code: bash <(9p read agent/tools/agents/list_sessions.sh)
|
||||
```
|
||||
|
||||
Create session:
|
||||
```
|
||||
Tool: execute_code
|
||||
sandbox: anvilmcp
|
||||
code: bash <(9p read agent/tools/agents/create_session.sh) <backend> <cwd> [sandbox=<sandbox>] [model=<model>]
|
||||
```
|
||||
|
||||
Control session:
|
||||
```
|
||||
Tool: execute_code
|
||||
sandbox: anvilmcp
|
||||
code: bash <(9p read agent/tools/agents/control_session.sh) <session-id> <command>
|
||||
```
|
||||
|
||||
Spawn agent:
|
||||
```
|
||||
Tool: execute_code
|
||||
sandbox: default
|
||||
code: bash <(9p read agent/tools/agents/spawn_agent.sh) <agent-id> [cwd-path] [initial-context-prompt]
|
||||
```
|
||||
|
||||
Kill agent:
|
||||
```
|
||||
Tool: execute_code
|
||||
sandbox: anvilmcp
|
||||
code: bash <(9p read agent/tools/agents/kill_agent.sh) [cwd-path]
|
||||
```
|
||||
|
||||
## Backends
|
||||
|
||||
|
|
|
|||
|
|
@ -8,33 +8,123 @@ description: Manage tasks using the beads 9P interface. Use when creating, updat
|
|||
|
||||
## Project Mount Discovery
|
||||
|
||||
1. Find your project's beads mount: `MOUNT=$(bash <(9p read agent/tools/beads/list_mounts.sh) | grep "$(pwd)" | awk '{print $1}')`
|
||||
2. If no mount exists, create one and use it: `bash <(9p read agent/tools/beads/mount_beads.sh) "$(pwd)"`. The mount will be named for the base directory of $(pwd).
|
||||
Find your project's beads mount:
|
||||
```
|
||||
Tool: execute_code
|
||||
sandbox: anvilmcp
|
||||
code: MOUNT=$(bash <(9p read agent/tools/beads/list_mounts.sh) | grep "$(pwd)" | awk '{print $1}'); echo $MOUNT
|
||||
```
|
||||
|
||||
If no mount exists, create one:
|
||||
```
|
||||
Tool: execute_code
|
||||
sandbox: anvilmcp
|
||||
code: bash <(9p read agent/tools/beads/mount_beads.sh) "$(pwd)"
|
||||
```
|
||||
|
||||
## Tools
|
||||
|
||||
**IMPORTANT:** All beads tools listed must be run via `execute_code` tool.
|
||||
All tools require `<mount>` as first argument. Exceptions: `list_mounts.sh`, `mount_beads.sh`, `umount_beads.sh`.
|
||||
|
||||
**IMPORTANT:** All tools require `<mount>` as first argument. Exceptions: `list_mounts.sh`, `mount_beads.sh`, `umount_beads.sh`.
|
||||
List mounts:
|
||||
```
|
||||
Tool: execute_code
|
||||
sandbox: anvilmcp
|
||||
code: bash <(9p read agent/tools/beads/list_mounts.sh)
|
||||
```
|
||||
|
||||
- List mounts: `bash <(9p read tools/beads/list_mounts.sh)`
|
||||
- List ready beads: `bash <(9p read tools/beads/list_ready_beads.sh) <mount>`
|
||||
- List all beads: `bash <(9p read tools/beads/list_beads.sh) <mount>`
|
||||
- Read bead: `bash <(9p read tools/beads/read_bead.sh) <mount> <id> <property>`
|
||||
- Read bead JSON: `bash <(9p read tools/beads/read_bead.sh) <mount> <id> json`
|
||||
- Read bead comments: `bash <(9p read tools/beads/read_bead.sh) <mount> <id> comments`
|
||||
List ready beads:
|
||||
```
|
||||
Tool: execute_code
|
||||
sandbox: anvilmcp
|
||||
code: bash <(9p read agent/tools/beads/list_ready_beads.sh) <mount>
|
||||
```
|
||||
|
||||
List all beads:
|
||||
```
|
||||
Tool: execute_code
|
||||
sandbox: anvilmcp
|
||||
code: bash <(9p read agent/tools/beads/list_beads.sh) <mount>
|
||||
```
|
||||
|
||||
Read bead JSON:
|
||||
```
|
||||
Tool: execute_code
|
||||
sandbox: anvilmcp
|
||||
code: bash <(9p read agent/tools/beads/read_bead.sh) <mount> <id> json
|
||||
```
|
||||
|
||||
Read bead comments:
|
||||
```
|
||||
Tool: execute_code
|
||||
sandbox: anvilmcp
|
||||
code: bash <(9p read agent/tools/beads/read_bead.sh) <mount> <id> comments
|
||||
```
|
||||
|
||||
**IMPORTANT:** When working on a bead, always check `comment_count` in the JSON output. If > 0, read comments for additional context, decisions, or blockers.
|
||||
|
||||
- Claim bead: `bash <(9p read tools/beads/claim_bead.sh) <mount> bd-abc [$AGENT_ID]`
|
||||
- Complete bead: `bash <(9p read tools/beads/complete_bead.sh) <mount> bd-abc`
|
||||
- Fail bead: `bash <(9p read tools/beads/fail_bead.sh) <mount> bd-abc "reason"`
|
||||
- Create bead: `bash <(9p read tools/beads/create_bead.sh) <mount> "title" "desc" [parent]`
|
||||
- Update bead: `bash <(9p read tools/beads/update_bead.sh) <mount> bd-abc <field> <value>`
|
||||
- Add dependency: `bash <(9p read tools/beads/add_dependency.sh) <mount> <child> <parent>`
|
||||
- Comment on bead: `bash <(9p read tools/beads/comment_bead.sh) <mount> bd-abc "text"`
|
||||
- Label bead: `bash <(9p read tools/beads/label_bead.sh) <mount> bd-abc <label>`
|
||||
- Delete bead: `bash <(9p read tools/beads/delete_bead.sh) <mount> bd-abc`
|
||||
Claim bead:
|
||||
```
|
||||
Tool: execute_code
|
||||
sandbox: anvilmcp
|
||||
code: bash <(9p read agent/tools/beads/claim_bead.sh) <mount> <id> [$AGENT_ID]
|
||||
```
|
||||
|
||||
Complete bead:
|
||||
```
|
||||
Tool: execute_code
|
||||
sandbox: anvilmcp
|
||||
code: bash <(9p read agent/tools/beads/complete_bead.sh) <mount> <id>
|
||||
```
|
||||
|
||||
Fail bead:
|
||||
```
|
||||
Tool: execute_code
|
||||
sandbox: anvilmcp
|
||||
code: bash <(9p read agent/tools/beads/fail_bead.sh) <mount> <id> "reason"
|
||||
```
|
||||
|
||||
Create bead:
|
||||
```
|
||||
Tool: execute_code
|
||||
sandbox: anvilmcp
|
||||
code: bash <(9p read agent/tools/beads/create_bead.sh) <mount> "title" "desc" [parent]
|
||||
```
|
||||
|
||||
Update bead:
|
||||
```
|
||||
Tool: execute_code
|
||||
sandbox: anvilmcp
|
||||
code: bash <(9p read agent/tools/beads/update_bead.sh) <mount> <id> <field> <value>
|
||||
```
|
||||
|
||||
Add dependency:
|
||||
```
|
||||
Tool: execute_code
|
||||
sandbox: anvilmcp
|
||||
code: bash <(9p read agent/tools/beads/add_dependency.sh) <mount> <child> <parent>
|
||||
```
|
||||
|
||||
Comment on bead:
|
||||
```
|
||||
Tool: execute_code
|
||||
sandbox: anvilmcp
|
||||
code: bash <(9p read agent/tools/beads/comment_bead.sh) <mount> <id> "text"
|
||||
```
|
||||
|
||||
Label bead:
|
||||
```
|
||||
Tool: execute_code
|
||||
sandbox: anvilmcp
|
||||
code: bash <(9p read agent/tools/beads/label_bead.sh) <mount> <id> <label>
|
||||
```
|
||||
|
||||
Delete bead:
|
||||
```
|
||||
Tool: execute_code
|
||||
sandbox: anvilmcp
|
||||
code: bash <(9p read agent/tools/beads/delete_bead.sh) <mount> <id>
|
||||
```
|
||||
|
||||
**Note:** Delete does not cascade. To delete a parent and all children, delete children first, then parent.
|
||||
|
||||
|
|
|
|||
|
|
@ -10,10 +10,10 @@ Import GitHub issues into beads.
|
|||
|
||||
## Usage
|
||||
|
||||
All commands must be run via `execute_code` tool.
|
||||
|
||||
```bash
|
||||
github_to_beads.sh OWNER/REPO ISSUE_NUMBER
|
||||
```
|
||||
Tool: execute_code
|
||||
sandbox: anvilmcp
|
||||
code: bash <(9p read agent/tools/github/github_to_beads.sh) OWNER/REPO ISSUE_NUMBER
|
||||
```
|
||||
|
||||
**What it does:**
|
||||
|
|
|
|||
|
|
@ -10,10 +10,10 @@ Import Jira ticket hierarchies into beads.
|
|||
|
||||
## Usage
|
||||
|
||||
All commands must be run via `execute_code` tool.
|
||||
|
||||
```bash
|
||||
jira_to_beads.sh PROJ-12345
|
||||
```
|
||||
Tool: execute_code
|
||||
sandbox: anvilmcp
|
||||
code: bash <(9p read agent/tools/jira/jira_to_beads.sh) PROJ-12345
|
||||
```
|
||||
|
||||
**What it does:**
|
||||
|
|
|
|||
|
|
@ -37,15 +37,33 @@ Load pcloudcc-testing skill. Test the affected code path with unit tests, fault
|
|||
|
||||
**Prerequisites**: Load `anvillm-sessions` and `anvillm-communication` skills
|
||||
|
||||
**Spawn testing agent**: `bash <(9p read agent/tools/agents/spawn_agent.sh) <your-id> "<system-prompt-above>"`
|
||||
Spawn testing agent:
|
||||
```
|
||||
Tool: execute_code
|
||||
sandbox: default
|
||||
code: bash <(9p read agent/tools/agents/spawn_agent.sh) <your-id> "<system-prompt-above>"
|
||||
```
|
||||
|
||||
**Send test request**: `bash <(9p read agent/tools/messaging/send_message.sh) <your-id> <testing-agent-id> PROMPT_REQUEST "Validate fix" "Bug: <desc>\nFix: <changes>\nFiles: <list>\nBranch: <name>"`
|
||||
Send test request:
|
||||
```
|
||||
Tool: execute_code
|
||||
sandbox: anvilmcp
|
||||
code: bash <(9p read agent/tools/messaging/send_message.sh) <your-id> <testing-agent-id> PROMPT_REQUEST "Validate fix" "Bug: <desc>\nFix: <changes>\nFiles: <list>\nBranch: <name>"
|
||||
```
|
||||
|
||||
**Read response** (user notifies when ready): `bash <(9p read agent/tools/messaging/read_inbox.sh) <your-id>`
|
||||
Read response (user notifies when ready):
|
||||
```
|
||||
Tool: execute_code
|
||||
sandbox: anvilmcp
|
||||
code: bash <(9p read agent/tools/messaging/read_inbox.sh) <your-id>
|
||||
```
|
||||
|
||||
**Kill testing agent**: `bash <(9p read agent/tools/agents/kill_agent.sh) <testing-agent-id>`
|
||||
|
||||
**Note**: `spawn_agent.sh` is an exception — use the Bash tool (`execute_bash`), NOT `execute_code`. All other commands in this section use `execute_code` as normal.
|
||||
Kill testing agent:
|
||||
```
|
||||
Tool: execute_code
|
||||
sandbox: anvilmcp
|
||||
code: bash <(9p read agent/tools/agents/kill_agent.sh) <testing-agent-id>
|
||||
```
|
||||
|
||||
### Testing Agent Workflow
|
||||
|
||||
|
|
@ -54,7 +72,12 @@ Load pcloudcc-testing skill. Test the affected code path with unit tests, fault
|
|||
3. Design targeted test (reproduce bug scenario)
|
||||
4. Execute test (unit/fault-inject/smoke)
|
||||
5. Verify fix (old fails, new passes)
|
||||
6. Report: `bash <(9p read agent/tools/messaging/send_message.sh) <your-id> <requester-id> PROMPT_RESPONSE "Validation complete" "Status: completed|failed\n<evidence>"`
|
||||
6. Report:
|
||||
```
|
||||
Tool: execute_code
|
||||
sandbox: anvilmcp
|
||||
code: bash <(9p read agent/tools/messaging/send_message.sh) <your-id> <requester-id> PROMPT_RESPONSE "Validation complete" "Status: completed|failed\n<evidence>"
|
||||
```
|
||||
|
||||
**FUSE testing**: Inform user daemon must start outside sandbox, wait for acknowledgement
|
||||
|
||||
|
|
|
|||
Reference in New Issue