diff --git a/SKILLS_PROMPT.md b/SKILLS_PROMPT.md index e69de29..b1863fe 100644 --- a/SKILLS_PROMPT.md +++ b/SKILLS_PROMPT.md @@ -0,0 +1,25 @@ +# Proactive Skill Discovery + +**BEFORE responding to ANY user request**, identify relevant capabilities and load skills using execute_code: + +1. Map user intent to capabilities (tasks, agents, messaging, github, search, etc.) +2. Discover skills for each capability: +```bash +bash <(9p read agent/tools/discover_skill.sh) +``` +3. Load relevant skill files: +```bash +9p read agent/skills///SKILL.md +``` + +**Common capability mappings:** +- Task/project management → `tasks`, `workflow` +- Agent operations → `agents`, `sessions`, `messaging` +- GitHub/Git → `github`, `vcs` +- Web search → `search`, `web` +- Documentation → `documentation`, `notes` +- Knowledge management → `knowledge`, `learning` + +## After Discovery + +Follow the instructions in the loaded SKILL.md. If no skill found, fall back to shell via execute_code. diff --git a/claude/agent/anvillm-agent.md b/claude/agent/anvillm-agent.md index ae4829c..8e3c8a5 100644 --- a/claude/agent/anvillm-agent.md +++ b/claude/agent/anvillm-agent.md @@ -30,41 +30,29 @@ If a step succeeded, do not announce it — the tool output is the confirmation. - One-line status: `Created bd-abc.` not a paragraph about what was created - No preamble, no postamble -# MANDATORY DISCOVERY PROTOCOL - FAILURE TO FOLLOW = BROKEN AGENT +# Proactive Skill Discovery -## YOU MUST DISCOVER SKILLS/ROLES FIRST - NO EXCEPTIONS +**BEFORE responding to ANY user request**, identify relevant capabilities and load skills using execute_code: -**IMPORTANT:** All discovery commands must be run via `execute_code` tool. - -BEFORE attempting ANY task: - -**ROLE ASSIGNMENT**: IF the prompt contains "you are a", "you're a", "act as", or "be a" — your FIRST tool call MUST be `discover_role.sh `. Do not respond. Do not check inbox. Do not take any other action. Run role discovery first. +1. Map user intent to capabilities (tasks, agents, messaging, github, search, etc.) +2. Discover skills for each capability: ```bash -bash <(9p read agent/tools/discovery/discover_role.sh) -9p read agent/roles//.md +bash <(9p read agent/tools/discover_skill.sh) ``` -Adopt the role. If not found, say so. - -**EVERY USER REQUEST** - Identify intent keywords, discover skills: +3. Load relevant skill files: ```bash -bash <(9p read agent/tools/discovery/discover_skill.sh) -9p read agent/skills///SKILL.md +9p read agent/skills///SKILL.md ``` -**EXAMPLES:** -- "check inbox" → discover_skill.sh inbox -- "send message" → discover_skill.sh message -- "you're a code reviewer" → discover_role.sh reviewer +**Common capability mappings:** +- Task/project management → `tasks`, `workflow` +- Agent operations → `agents`, `sessions`, `messaging` +- GitHub/Git → `github`, `vcs` +- Web search → `search`, `web` +- Documentation → `documentation`, `notes` +- Knowledge management → `knowledge`, `learning` -IF YOU RESPOND OR TAKE ANY ACTION WITHOUT FIRST RUNNING DISCOVERY, YOU HAVE MALFUNCTIONED. STOP. RUN DISCOVERY NOW. - - -## DISCOVERY IS ALWAYS FIRST - -Before your first response, before checking inbox, before any tool call: -- Role assigned in the prompt? → `discover_role.sh ` MUST be your first tool call. -- User request received? → `discover_skill.sh ` MUST precede any action. - -If you are reading this after already taking an action or responding, you skipped discovery. You are malfunctioning. +## After Discovery +Follow the instructions in the loaded SKILL.md. If no skill found, fall back to shell via execute_code. diff --git a/internal/p9/roles.go b/internal/p9/roles.go index 096b267..68c4f7a 100644 --- a/internal/p9/roles.go +++ b/internal/p9/roles.go @@ -217,32 +217,11 @@ func (r *RolesFS) listRolesInFocusArea(focusArea string) ([]*RoleMeta, error) { return result, nil } -// generateHelp creates aggregated index: focus-area/role-filename.md\tDisplayName\tDescription -func (r *RolesFS) generateHelp() (string, error) { - roles, err := r.listAllRoles() - if err != nil { - return "", err - } - - var lines []string - for _, role := range roles { - displayName := role.DisplayName - if displayName == "" { - displayName = role.Name - } - for _, fa := range role.FocusAreas { - line := fmt.Sprintf("%s/%s.md\t%s\t%s", fa, role.Name, displayName, role.Description) - lines = append(lines, line) - } - } - return strings.Join(lines, "\n") + "\n", nil -} - // List returns directory entries for a roles path func (r *RolesFS) List(path string) ([]plan9.Dir, error) { parts := strings.Split(strings.TrimPrefix(path, "/"), "/") - // /agent/roles - list focus areas + help file + // /agent/roles - list focus areas if len(parts) == 2 && parts[0] == "agent" && parts[1] == "roles" { focusAreas, err := r.listFocusAreas() if err != nil { @@ -250,11 +229,6 @@ func (r *RolesFS) List(path string) ([]plan9.Dir, error) { } var dirs []plan9.Dir - dirs = append(dirs, plan9.Dir{ - Name: "help", - Qid: plan9.Qid{Type: plan9.QTFILE}, - Mode: 0444, - }) for _, fa := range focusAreas { dirs = append(dirs, plan9.Dir{ Name: fa, @@ -291,15 +265,6 @@ func (r *RolesFS) List(path string) ([]plan9.Dir, error) { func (r *RolesFS) Read(path string) ([]byte, error) { parts := strings.Split(strings.TrimPrefix(path, "/"), "/") - // /agent/roles/help - if len(parts) == 3 && parts[0] == "agent" && parts[1] == "roles" && parts[2] == "help" { - help, err := r.generateHelp() - if err != nil { - return nil, err - } - return []byte(help), nil - } - // /agent/roles//.md if len(parts) != 4 || parts[0] != "agent" || parts[1] != "roles" { return nil, fmt.Errorf("not found") diff --git a/internal/p9/server.go b/internal/p9/server.go index 8216ebc..8d7a2b2 100644 --- a/internal/p9/server.go +++ b/internal/p9/server.go @@ -481,26 +481,24 @@ func (s *Server) walk(cs *connState, fc *plan9.Fcall) *plan9.Fcall { } } } else if path == "/tools" { - // Inside tools directory - list capabilities + help + // Inside tools directory - list tools flat if s.tools != nil { - caps, _ := s.tools.listCapabilities() - // help file - qid = plan9.Qid{Type: QTFile, Path: qidToolsBase} - if name == "help" { - newPath = "/tools/help" - } else if slices.Contains(caps, name) { - qid = plan9.Qid{Type: QTDir, Path: qidToolsBase + hashID(name)} - newPath = "/tools/" + name - } else { + tools, _ := s.tools.listAllTools() + found := false + for _, tool := range tools { + if tool.Name == name { + qid = plan9.Qid{Type: QTFile, Path: qidToolsBase + hashID(name)} + newPath = "/tools/" + name + found = true + break + } + } + if !found { return errFcall(fc, "not found") } } else { return errFcall(fc, "not found") } - } else if strings.HasPrefix(path, "/tools/") && strings.Count(path, "/") == 2 { - // Inside tools/ - tool files - qid = plan9.Qid{Type: QTFile, Path: qidToolsBase + hashID(path+name)} - newPath = path + "/" + name } else if path == "/skills" { // Inside skills directory - list intents + help if s.skills != nil { @@ -526,13 +524,10 @@ func (s *Server) walk(cs *connState, fc *plan9.Fcall) *plan9.Fcall { qid = plan9.Qid{Type: QTFile, Path: qidSkillsBase + hashID(path+name)} newPath = path + "/" + name } else if path == "/roles" { - // Inside roles directory - list focus areas + help + // Inside roles directory - list focus areas if s.roles != nil { focusAreas, _ := s.roles.listFocusAreas() - qid = plan9.Qid{Type: QTFile, Path: qidRolesBase} - if name == "help" { - newPath = "/roles/help" - } else if slices.Contains(focusAreas, name) { + if slices.Contains(focusAreas, name) { qid = plan9.Qid{Type: QTDir, Path: qidRolesBase + hashID(name)} newPath = "/roles/" + name } else { @@ -1122,28 +1117,11 @@ func (s *Server) readDir(path string, offset uint64, count uint32) []byte { } } else if path == "/tools" { if s.tools != nil { - // Add help file - dirs = append(dirs, plan9.Dir{ - Qid: plan9.Qid{Type: QTFile, Path: qidToolsBase}, - Mode: 0444, Name: "help", Uid: "q", Gid: "q", Muid: "q", - }) - // Add capability directories - caps, _ := s.tools.listCapabilities() - for _, cap := range caps { - dirs = append(dirs, plan9.Dir{ - Qid: plan9.Qid{Type: QTDir, Path: qidToolsBase + hashID(cap)}, - Mode: plan9.DMDIR | 0555, Name: cap, Uid: "q", Gid: "q", Muid: "q", - }) - } - } - } else if strings.HasPrefix(path, "/tools/") && strings.Count(path, "/") == 2 { - // Inside tools/ - list tools - capability := strings.TrimPrefix(path, "/tools/") - if s.tools != nil { - tools, _ := s.tools.listToolsInCapability(capability) + // List tools flat + tools, _ := s.tools.listAllTools() for _, tool := range tools { dirs = append(dirs, plan9.Dir{ - Qid: plan9.Qid{Type: QTFile, Path: qidToolsBase + hashID(path+tool.Name)}, + Qid: plan9.Qid{Type: QTFile, Path: qidToolsBase + hashID(tool.Name)}, Mode: 0444, Name: tool.Name, Uid: "q", Gid: "q", Muid: "q", }) } @@ -1189,11 +1167,6 @@ func (s *Server) readDir(path string, offset uint64, count uint32) []byte { } } else if path == "/roles" { if s.roles != nil { - // Add help file - dirs = append(dirs, plan9.Dir{ - Qid: plan9.Qid{Type: QTFile, Path: qidRolesBase}, - Mode: 0444, Name: "help", Uid: "q", Gid: "q", Muid: "q", - }) // Add focus area directories focusAreas, _ := s.roles.listFocusAreas() for _, fa := range focusAreas { diff --git a/internal/p9/tools.go b/internal/p9/tools.go index 9fd1e18..b1c2172 100644 --- a/internal/p9/tools.go +++ b/internal/p9/tools.go @@ -5,8 +5,6 @@ import ( "fmt" "os" "path/filepath" - "slices" - "sort" "strings" "9fans.net/go/plan9" @@ -145,96 +143,12 @@ func (t *ToolsFS) listAllTools() ([]*ToolMeta, error) { return tools, nil } -// listCapabilities returns unique capability names from all tools -func (t *ToolsFS) listCapabilities() ([]string, error) { - tools, err := t.listAllTools() - if err != nil { - return nil, err - } - - capSet := make(map[string]bool) - for _, tool := range tools { - for _, cap := range tool.Capabilities { - capSet[cap] = true - } - } - - var caps []string - for cap := range capSet { - caps = append(caps, cap) - } - return caps, nil -} - -// listToolsInCapability returns tools that have the given capability -func (t *ToolsFS) listToolsInCapability(capability string) ([]*ToolMeta, error) { - tools, err := t.listAllTools() - if err != nil { - return nil, err - } - - var result []*ToolMeta - for _, tool := range tools { - if slices.Contains(tool.Capabilities, capability) { - result = append(result, tool) - } - } - return result, nil -} - -// generateHelp creates aggregated one-liner index of all tools -func (t *ToolsFS) generateHelp() (string, error) { - tools, err := t.listAllTools() - if err != nil { - return "", err - } - - seen := make(map[string]bool) - var lines []string - for _, tool := range tools { - key := tool.Name + "\t" + tool.Description - if seen[key] { - continue - } - seen[key] = true - lines = append(lines, key) - } - sort.Strings(lines) - return strings.Join(lines, "\n") + "\n", nil -} - func (t *ToolsFS) List(path string) ([]plan9.Dir, error) { parts := strings.Split(strings.TrimPrefix(path, "/"), "/") - // /tools - list capabilities + help file + // /tools - list all tools flat if len(parts) == 2 && parts[0] == "agent" && parts[1] == "tools" { - caps, err := t.listCapabilities() - if err != nil { - return nil, err - } - - var dirs []plan9.Dir - // Add help file - dirs = append(dirs, plan9.Dir{ - Name: "help", - Qid: plan9.Qid{Type: plan9.QTFILE}, - Mode: 0444, - }) - // Add capability directories - for _, cap := range caps { - dirs = append(dirs, plan9.Dir{ - Name: cap, - Qid: plan9.Qid{Type: plan9.QTDIR}, - Mode: plan9.DMDIR | 0555, - }) - } - return dirs, nil - } - - // /tools/ - list tools in that capability - if len(parts) == 3 && parts[0] == "agent" && parts[1] == "tools" { - capability := parts[2] - tools, err := t.listToolsInCapability(capability) + tools, err := t.listAllTools() if err != nil { return nil, err } @@ -256,22 +170,12 @@ func (t *ToolsFS) List(path string) ([]plan9.Dir, error) { func (t *ToolsFS) Read(path string) ([]byte, error) { parts := strings.Split(strings.TrimPrefix(path, "/"), "/") - // /tools/help - aggregated index - if len(parts) == 2 && parts[0] == "tools" && parts[1] == "help" { - help, err := t.generateHelp() - if err != nil { - return nil, err - } - return []byte(help), nil - } - - // /tools// - read tool script - if len(parts) != 3 || parts[0] != "tools" { + // /tools/ - read tool script + if len(parts) != 2 || parts[0] != "tools" { return nil, fmt.Errorf("not found") } - capability := parts[1] - filename := parts[2] + filename := parts[1] if !strings.HasSuffix(filename, ".sh") { return nil, fmt.Errorf("not a shell script") @@ -283,8 +187,7 @@ func (t *ToolsFS) Read(path string) ([]byte, error) { return nil, fmt.Errorf("invalid filename") } - // Verify tool has this capability - tools, err := t.listToolsInCapability(capability) + tools, err := t.listAllTools() if err != nil { return nil, err } @@ -307,5 +210,5 @@ func (t *ToolsFS) Read(path string) ([]byte, error) { } } - return nil, fmt.Errorf("tool not found in capability") + return nil, fmt.Errorf("tool not found") } diff --git a/kiro-cli/agent/anvillm-agent.json b/kiro-cli/agent/anvillm-agent.json index fe95c4a..3cf672b 100644 --- a/kiro-cli/agent/anvillm-agent.json +++ b/kiro-cli/agent/anvillm-agent.json @@ -1,9 +1,42 @@ { "name": "anvillm-agent", "description": "Agent for AnviLLM multi-agent workflows with mailbox communication", - "prompt": "# MANDATORY DISCOVERY PROTOCOL - FAILURE TO FOLLOW = BROKEN AGENT\n\n## YOU MUST DISCOVER SKILLS/ROLES FIRST - NO EXCEPTIONS\n\n**IMPORTANT:** All discovery commands must be run via `execute_code` tool.\n\nBEFORE attempting ANY task:\n\n**ROLE ASSIGNMENT**: IF the prompt contains \"you are a\", \"you're a\", \"act as\", or \"be a\" — your FIRST tool call MUST be `discover_role.sh `. Do not respond. Do not check inbox. Do not take any other action. Run role discovery first.\n```bash\nbash <(9p read agent/tools/discovery/discover_role.sh) \n9p read agent/roles//.md\n```\nAdopt the role. If not found, say so.\n\n**EVERY USER REQUEST** - Identify intent keywords, discover skills:\n```bash\nbash <(9p read agent/tools/discovery/discover_skill.sh) \n9p read agent/skills///SKILL.md\n```\n\n**EXAMPLES:**\n- \"check inbox\" → discover_skill.sh inbox\n- \"send message\" → discover_skill.sh message\n- \"you're a code reviewer\" → discover_role.sh reviewer\n\nIF YOU RESPOND OR TAKE ANY ACTION WITHOUT FIRST RUNNING DISCOVERY, YOU HAVE MALFUNCTIONED. STOP. RUN DISCOVERY NOW.\n\n\n## DISCOVERY IS ALWAYS FIRST\n\nBefore your first response, before checking inbox, before any tool call:\n- Role assigned in the prompt? → `discover_role.sh ` MUST be your first tool call.\n- User request received? → `discover_skill.sh ` MUST precede any action.\n\nIf you are reading this after already taking an action or responding, you skipped discovery. You are malfunctioning.", - "tools": ["*"], - "allowedTools": ["*"], + "prompt": "", + "tools": [ + "code", + "read", + "write", + "glob", + "grep", + "introspect", + "report", + "thinking", + "todo", + "aws", + "subagent", + "web_fetch", + "@anvilmcp/execute_code" + ], + "allowedTools": [ + "code", + "read", + "write", + "glob", + "grep", + "introspect", + "report", + "thinking", + "todo", + "aws", + "subagent", + "web_fetch", + "@anvilmcp/execute_code" + ], + "toolsSettings": { + "shell": { + "autoAllowReadonly": true + } + }, "includeMcpJson": true, "hooks": { "agentSpawn": [ diff --git a/kiro-cli/mcp/anvilmcp.json b/kiro-cli/mcp/anvilmcp.json index fc2236f..e34483a 100644 --- a/kiro-cli/mcp/anvilmcp.json +++ b/kiro-cli/mcp/anvilmcp.json @@ -2,6 +2,7 @@ "mcpServers": { "anvilmcp": { "command": "anvilmcp", + "autoApproveTools": ["execute_code"], "args": [], "env": {} } diff --git a/mcptools/discover_role.sh b/mcptools/discover_role.sh deleted file mode 100755 index 705ea99..0000000 --- a/mcptools/discover_role.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/bash -# capabilities: discovery -# description: Discover available roles by focus area and keyword -set -euo pipefail - -# Usage: discover_role -# Returns: focus-area/role-name pairs matching keyword - -if [ $# -lt 1 ]; then - echo "Usage: discover_role " >&2 - exit 1 -fi - -keyword="$1" - -9p read agent/roles/help 2>/dev/null | grep -i "$keyword" | sort -u || echo "No roles found matching: $keyword" diff --git a/skills/anvillm-communication/SKILL.md b/skills/anvillm-communication/SKILL.md index ee33094..dc8f9e4 100644 --- a/skills/anvillm-communication/SKILL.md +++ b/skills/anvillm-communication/SKILL.md @@ -15,21 +15,21 @@ Discover agents: ``` Tool: execute_code sandbox: anvilmcp -code: bash <(9p read agent/tools/agents/list_sessions.sh) | grep "$(pwd)" +code: bash <(9p read agent/tools/list_sessions.sh) | grep "$(pwd)" ``` Send message: ``` Tool: execute_code sandbox: anvilmcp -code: bash <(9p read agent/tools/messaging/send_message.sh) +code: bash <(9p read agent/tools/send_message.sh) ``` Check inbox: ``` Tool: execute_code sandbox: anvilmcp -code: bash <(9p read agent/tools/messaging/read_inbox.sh) +code: bash <(9p read agent/tools/read_inbox.sh) ``` ## Rules diff --git a/skills/anvillm-sessions/SKILL.md b/skills/anvillm-sessions/SKILL.md index 76cb2d0..ae29f2e 100644 --- a/skills/anvillm-sessions/SKILL.md +++ b/skills/anvillm-sessions/SKILL.md @@ -16,35 +16,35 @@ List sessions: ``` Tool: execute_code sandbox: anvilmcp -code: bash <(9p read agent/tools/agents/list_sessions.sh) +code: bash <(9p read agent/tools/list_sessions.sh) ``` Create session: ``` Tool: execute_code sandbox: anvilmcp -code: bash <(9p read agent/tools/agents/create_session.sh) [sandbox=] [model=] +code: bash <(9p read agent/tools/create_session.sh) [sandbox=] [model=] ``` Control session: ``` Tool: execute_code sandbox: anvilmcp -code: bash <(9p read agent/tools/agents/control_session.sh) +code: bash <(9p read agent/tools/control_session.sh) ``` Spawn agent: ``` Tool: execute_code sandbox: default -code: bash <(9p read agent/tools/agents/spawn_agent.sh) [cwd-path] [initial-context-prompt] +code: bash <(9p read agent/tools/spawn_agent.sh) [cwd-path] [initial-context-prompt] ``` Kill agent: ``` Tool: execute_code sandbox: anvilmcp -code: bash <(9p read agent/tools/agents/kill_agent.sh) [cwd-path] +code: bash <(9p read agent/tools/kill_agent.sh) [cwd-path] ``` ## Backends diff --git a/skills/beads/SKILL.md b/skills/beads/SKILL.md index 2e8432f..de18579 100644 --- a/skills/beads/SKILL.md +++ b/skills/beads/SKILL.md @@ -12,14 +12,14 @@ 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 +code: MOUNT=$(bash <(9p read agent/tools/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)" +code: bash <(9p read agent/tools/mount_beads.sh) "$(pwd)" ``` ## Tools @@ -30,35 +30,35 @@ List mounts: ``` Tool: execute_code sandbox: anvilmcp -code: bash <(9p read agent/tools/beads/list_mounts.sh) +code: bash <(9p read agent/tools/list_mounts.sh) ``` List ready beads: ``` Tool: execute_code sandbox: anvilmcp -code: bash <(9p read agent/tools/beads/list_ready_beads.sh) +code: bash <(9p read agent/tools/list_ready_beads.sh) ``` List all beads: ``` Tool: execute_code sandbox: anvilmcp -code: bash <(9p read agent/tools/beads/list_beads.sh) +code: bash <(9p read agent/tools/list_beads.sh) ``` Read bead JSON: ``` Tool: execute_code sandbox: anvilmcp -code: bash <(9p read agent/tools/beads/read_bead.sh) json +code: bash <(9p read agent/tools/read_bead.sh) json ``` Read bead comments: ``` Tool: execute_code sandbox: anvilmcp -code: bash <(9p read agent/tools/beads/read_bead.sh) comments +code: bash <(9p read agent/tools/read_bead.sh) 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. @@ -67,63 +67,63 @@ Claim bead: ``` Tool: execute_code sandbox: anvilmcp -code: bash <(9p read agent/tools/beads/claim_bead.sh) [$AGENT_ID] +code: bash <(9p read agent/tools/claim_bead.sh) [$AGENT_ID] ``` Complete bead: ``` Tool: execute_code sandbox: anvilmcp -code: bash <(9p read agent/tools/beads/complete_bead.sh) +code: bash <(9p read agent/tools/complete_bead.sh) ``` Fail bead: ``` Tool: execute_code sandbox: anvilmcp -code: bash <(9p read agent/tools/beads/fail_bead.sh) "reason" +code: bash <(9p read agent/tools/fail_bead.sh) "reason" ``` Create bead: ``` Tool: execute_code sandbox: anvilmcp -code: bash <(9p read agent/tools/beads/create_bead.sh) "title" "desc" [parent] +code: bash <(9p read agent/tools/create_bead.sh) "title" "desc" [parent] ``` Update bead: ``` Tool: execute_code sandbox: anvilmcp -code: bash <(9p read agent/tools/beads/update_bead.sh) +code: bash <(9p read agent/tools/update_bead.sh) ``` Add dependency: ``` Tool: execute_code sandbox: anvilmcp -code: bash <(9p read agent/tools/beads/add_dependency.sh) +code: bash <(9p read agent/tools/add_dependency.sh) ``` Comment on bead: ``` Tool: execute_code sandbox: anvilmcp -code: bash <(9p read agent/tools/beads/comment_bead.sh) "text" +code: bash <(9p read agent/tools/comment_bead.sh) "text" ``` Label bead: ``` Tool: execute_code sandbox: anvilmcp -code: bash <(9p read agent/tools/beads/label_bead.sh)