hotfixes, msg trace diagram generator
This commit is contained in:
parent
50ae1d0dad
commit
5bba76d2b9
|
|
@ -4,6 +4,7 @@ Q-Reset
|
|||
Q-Login
|
||||
/anvillm
|
||||
/anvilweb
|
||||
/anvilmcp
|
||||
.claude
|
||||
.kiro
|
||||
*.elc
|
||||
|
|
|
|||
|
|
@ -1,13 +1,6 @@
|
|||
#!/bin/bash
|
||||
# Conductor - Orchestration bot that manages other bots and routes tasks
|
||||
# Usage: ./bot-templates/Conductor <backend> <workdir> [< custom-prompt.txt]
|
||||
#
|
||||
# The context prompt is read from stdin when stdin is not a terminal,
|
||||
# letting users supply a customized prompt file without editing this script.
|
||||
# Example:
|
||||
# ./bot-templates/Conductor claude /path/to/project < my-conductor-prompt.txt
|
||||
#
|
||||
# When stdin is a terminal (interactive), the built-in default prompt is used.
|
||||
# Usage: ./bot-templates/Conductor <backend> <workdir>
|
||||
|
||||
set -e
|
||||
|
||||
|
|
@ -36,10 +29,7 @@ CONDUCTOR_ID=$(9p read $AGENT_MOUNT/list | head -1 | awk '{print $1}')
|
|||
|
||||
echo "$ALIAS_NAME" | 9p write $AGENT_MOUNT/$CONDUCTOR_ID/alias
|
||||
|
||||
# Determine prompt source: stdin (piped file) or built-in default.
|
||||
if [ -t 0 ]; then
|
||||
# stdin is a terminal — use the built-in default prompt.
|
||||
CONTEXT=$(cat <<EOF
|
||||
cat <<EOF | 9p write $AGENT_MOUNT/$CONDUCTOR_ID/context
|
||||
You are the Conductor - an orchestration bot that DELEGATES work to other bots.
|
||||
|
||||
CRITICAL: You DO NOT implement solutions, write code, or do the work yourself. Your ONLY job is to DELEGATE to specialized bots.
|
||||
|
|
@ -52,7 +42,7 @@ Workflow:
|
|||
3. Read its child beads using "9p read agent/beads/children/<parent-id>"
|
||||
4. For each child bead, start a specialized bot and tell it to claim and work on that child
|
||||
5. Bots will send completion messages to your inbox when done (messages arrive automatically)
|
||||
6. When a bot notifies you it's done, kill it: echo "kill" | 9p write agent/<bot-id>/ctl
|
||||
6. When a bot notifies you it's done, acknowledge and shut down that bot
|
||||
7. Check if ALL children are complete by reading "9p read agent/beads/children/<parent-id> | jq 'all(.status == \"closed\")'"
|
||||
8. Only mark the parent bead complete when all children have status==closed
|
||||
|
||||
|
|
@ -61,83 +51,16 @@ Core skills: beads, anvillm-communication
|
|||
When creating a bot:
|
||||
1. Start the session: echo "new <backend> <workdir>" | 9p write agent/ctl
|
||||
2. Get the bot's session ID from agent/list
|
||||
3. Set a descriptive alias (max 16 characters): echo "<alias>" | 9p write agent/<bot-id>/alias
|
||||
Derive the alias from the bot's role and bead ID, e.g. "dev-i4h.1", "review-i4h",
|
||||
"plan-abc.3". Truncate to 16 characters if needed.
|
||||
4. Write context to agent/<bot-id>/context that includes:
|
||||
3. Write context to agent/<bot-id>/context that includes:
|
||||
- The bot's role and task
|
||||
- Instructions to send completion messages to you (your AGENT_ID is $CONDUCTOR_ID)
|
||||
- Instructions to send clarification requests to you when blocked
|
||||
- Instructions to report failures and rejected approvals to you
|
||||
- Example:
|
||||
"When done: call complete on your bead, then send PROMPT_RESPONSE to $CONDUCTOR_ID.
|
||||
If you need clarification: send QUERY_REQUEST to $CONDUCTOR_ID.
|
||||
If your work fails or is rejected: call fail on your bead with the reason, then send
|
||||
PROMPT_RESPONSE to $CONDUCTOR_ID describing what failed and why."
|
||||
- Example: "When done, send PROMPT_RESPONSE to $CONDUCTOR_ID. If you need clarification, send QUERY_REQUEST to $CONDUCTOR_ID."
|
||||
|
||||
Message routing:
|
||||
- When a bot needs clarification, forward the query to the user with type QUERY_REQUEST
|
||||
- When the user responds, relay the answer back to the bot
|
||||
- When a bot reports failure, choose based on the reason:
|
||||
RETRY — transient or recoverable failure (timeout, rate limit, bot got confused/stuck,
|
||||
tool temporarily unavailable). Spawn a fresh bot for the same bead.
|
||||
ESCALATE — failure requires human judgment. ALWAYS escalate on a rejected approval or
|
||||
review: the human may want to adjust the task, relax the constraint, or give
|
||||
you different instructions — do not treat rejection as a hard abort.
|
||||
Also escalate for: ambiguous blocker, missing credentials or access, task
|
||||
definition needs clarification, or repeated retries have not helped.
|
||||
Send QUERY_REQUEST to the user describing what failed and asking how to proceed.
|
||||
FAIL — work is definitively uncompletable without replanning: a required dependency
|
||||
cannot be satisfied, the user has directed you to abort, or escalation produced
|
||||
no resolution. Call fail on the parent bead.
|
||||
|
||||
## Approval Gates (Human in the Loop)
|
||||
|
||||
Some actions REQUIRE human approval before proceeding. These are actions that are
|
||||
irreversible, destructive, high-risk, or have been explicitly flagged requires_approval.
|
||||
|
||||
### Actions That Require Approval
|
||||
|
||||
Before performing any of the following, send an APPROVAL_REQUEST to the user and
|
||||
wait for an APPROVAL_RESPONSE:
|
||||
|
||||
- Deleting beads or significant work items
|
||||
- Spawning more than 5 sub-agents simultaneously
|
||||
- Any action on beads labeled "requires_approval" or "requires_review"
|
||||
- Destructive file system operations (deleting source files, truncating databases)
|
||||
- Publishing or deploying code to production systems
|
||||
- Any operation the user has explicitly flagged as requiring review
|
||||
|
||||
### Approval Workflow
|
||||
|
||||
1. Send APPROVAL_REQUEST to user ("user") via mailbox with:
|
||||
- subject: brief summary of the action requiring approval
|
||||
- body: full description of what will happen, the risk, and why you want to proceed
|
||||
|
||||
2. Atomically mark the bead pending and assign it to the user for review:
|
||||
echo "pending-approval <bead-id>" | 9p write agent/beads/ctl
|
||||
|
||||
3. Wait for the user's APPROVAL_RESPONSE in your inbox (check periodically with read_inbox).
|
||||
|
||||
4. On approval: resume the bead and reassign it back to yourself:
|
||||
echo "resume <bead-id> $CONDUCTOR_ID" | 9p write agent/beads/ctl
|
||||
|
||||
5. On rejection: fail the bead with the rejection reason:
|
||||
echo "fail <bead-id> 'human rejected: <reason>'" | 9p write agent/beads/ctl
|
||||
|
||||
### Timeout Handling
|
||||
|
||||
If no APPROVAL_RESPONSE arrives after a reasonable wait (suggest checking 3-5 times
|
||||
over ~10 minutes), send a QUERY_REQUEST to the user asking if they saw the approval
|
||||
request. Do NOT proceed without approval for any action in the list above.
|
||||
EOF
|
||||
)
|
||||
else
|
||||
# stdin is not a terminal — read custom prompt from stdin.
|
||||
CONTEXT=$(cat)
|
||||
fi
|
||||
|
||||
printf '%s' "$CONTEXT" | 9p write $AGENT_MOUNT/$CONDUCTOR_ID/context
|
||||
|
||||
echo ""
|
||||
echo "✓ Conductor workflow ready"
|
||||
|
|
|
|||
|
|
@ -1,13 +1,6 @@
|
|||
#!/bin/bash
|
||||
# Taskmaster - Task management agent workflow
|
||||
# Usage: ./bot-templates/Taskmaster <backend> <workdir> [< custom-prompt.txt]
|
||||
#
|
||||
# The context prompt is read from stdin when stdin is not a terminal,
|
||||
# letting users supply a customized prompt file without editing this script.
|
||||
# Example:
|
||||
# ./bot-templates/Taskmaster claude /path/to/project < my-taskmaster-prompt.txt
|
||||
#
|
||||
# When stdin is a terminal (interactive), the built-in default prompt is used.
|
||||
# Usage: ./bot-templates/Taskmaster <backend> <workdir>
|
||||
|
||||
set -e
|
||||
|
||||
|
|
@ -36,62 +29,11 @@ TASKMASTER_ID=$(9p read $AGENT_MOUNT/list | head -1 | awk '{print $1}')
|
|||
|
||||
echo "$ALIAS_NAME" | 9p write $AGENT_MOUNT/$TASKMASTER_ID/alias
|
||||
|
||||
# Determine prompt source: stdin (piped file) or built-in default.
|
||||
if [ -t 0 ]; then
|
||||
# stdin is a terminal — use the built-in default prompt.
|
||||
CONTEXT=$(cat <<'EOF'
|
||||
cat <<EOF | 9p write $AGENT_MOUNT/$TASKMASTER_ID/context
|
||||
You are a task management specialist. Do not implement solutions or write code. Core skills: beads, anvillm-communication.
|
||||
|
||||
You create and update tasks based on requirements using the beads skill, and pull context from Jira, GitHub, web search, or agent-kb to enrich details. You maintain clear descriptions and link sources.
|
||||
|
||||
## Approval and Review Labels
|
||||
|
||||
When creating or enriching a bead, assess the nature of the work and apply the
|
||||
appropriate label if the task meets the criteria below. Use your judgment — not
|
||||
every task needs a label, but high-risk or output-sensitive work should always
|
||||
be flagged so the Conductor and its agents gate correctly.
|
||||
|
||||
### requires_approval
|
||||
|
||||
Apply when completing the task would result in an action that is irreversible,
|
||||
destructive, or carries significant operational risk. Examples:
|
||||
|
||||
- Deleting or truncating data, files, or databases
|
||||
- Deploying or publishing to production systems
|
||||
- Changes to security settings, access controls, or credentials
|
||||
- Large-scale automated changes that are hard to roll back
|
||||
- Any action that spends money or triggers external billing
|
||||
- Modifications to shared infrastructure or configuration
|
||||
|
||||
Command: echo "label <bead-id> requires_approval" | 9p write agent/beads/ctl
|
||||
|
||||
### requires_review
|
||||
|
||||
Apply when the work produces output that a human should inspect and accept
|
||||
before it is considered done, even if the action itself is not destructive.
|
||||
Examples:
|
||||
|
||||
- Code changes to critical or security-sensitive paths
|
||||
- Public-facing content, documentation, or communications
|
||||
- Architectural or schema decisions
|
||||
- Changes to bot prompts, skills, or orchestration logic
|
||||
- Any task where correctness cannot be verified automatically
|
||||
|
||||
Command: echo "label <bead-id> requires_review" | 9p write agent/beads/ctl
|
||||
|
||||
### Both labels
|
||||
|
||||
A task may carry both labels — for example, a database migration that produces
|
||||
a migration script (requires_review) and then runs it in production
|
||||
(requires_approval).
|
||||
EOF
|
||||
)
|
||||
else
|
||||
# stdin is not a terminal — read custom prompt from stdin.
|
||||
CONTEXT=$(cat)
|
||||
fi
|
||||
|
||||
printf '%s' "$CONTEXT" | 9p write $AGENT_MOUNT/$TASKMASTER_ID/context
|
||||
|
||||
echo ""
|
||||
echo "✓ Taskmaster workflow ready"
|
||||
|
|
|
|||
|
|
@ -129,17 +129,6 @@ func main() {
|
|||
Required: []string{"agent_id", "state"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "load_skill",
|
||||
Description: "Load a skill file into context from $ANVILLM_SKILLS_PATH/{skill_name}/SKILL.md",
|
||||
InputSchema: InputSchema{
|
||||
Type: "object",
|
||||
Properties: map[string]Property{
|
||||
"skill_name": {Type: "string", Description: "Name of the skill to load"},
|
||||
},
|
||||
Required: []string{"skill_name"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "list_skills",
|
||||
Description: "List all available skills from $ANVILLM_SKILLS_PATH",
|
||||
|
|
@ -237,22 +226,6 @@ func handleToolCall(req MCPRequest) {
|
|||
{"type": "text", "text": "State set"},
|
||||
},
|
||||
})
|
||||
case "load_skill":
|
||||
skillName, _ := params.Arguments["skill_name"].(string)
|
||||
|
||||
fmt.Fprintf(os.Stderr, "[anvilmcp] Loading skill: %s\n", skillName)
|
||||
result, err := loadSkill(skillName)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "[anvilmcp] Error: %v\n", err)
|
||||
sendError(req.ID, -32000, err.Error())
|
||||
return
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, "[anvilmcp] Skill loaded: %d bytes\n", len(result))
|
||||
sendResponse(req.ID, map[string]interface{}{
|
||||
"content": []map[string]string{
|
||||
{"type": "text", "text": result},
|
||||
},
|
||||
})
|
||||
case "list_skills":
|
||||
fmt.Fprintf(os.Stderr, "[anvilmcp] Listing skills\n")
|
||||
result, err := listSkills()
|
||||
|
|
@ -378,25 +351,6 @@ func setState(agentID, state string) error {
|
|||
return cmd.Run()
|
||||
}
|
||||
|
||||
func loadSkill(skillName string) (string, error) {
|
||||
skillsPath := os.Getenv("ANVILLM_SKILLS_PATH")
|
||||
if skillsPath == "" {
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get home directory: %v", err)
|
||||
}
|
||||
skillsPath = fmt.Sprintf("%s/.config/anvillm/skills", home)
|
||||
}
|
||||
|
||||
skillPath := fmt.Sprintf("%s/%s/SKILL.md", skillsPath, skillName)
|
||||
data, err := os.ReadFile(skillPath)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to read skill file: %v", err)
|
||||
}
|
||||
|
||||
return string(data), nil
|
||||
}
|
||||
|
||||
func listSkills() (string, error) {
|
||||
skillsPath := os.Getenv("ANVILLM_SKILLS_PATH")
|
||||
if skillsPath == "" {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,107 @@
|
|||
#!/usr/bin/env python3
|
||||
"""AnviLLM message trace tool - PlantUML sequence diagram generator."""
|
||||
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import threading
|
||||
from http.server import HTTPServer, BaseHTTPRequestHandler
|
||||
|
||||
NAMESPACE = os.environ.get("NAMESPACE", f"/tmp/ns.{os.environ['USER']}.:0")
|
||||
PORT = 8089
|
||||
|
||||
events = []
|
||||
lock = threading.Lock()
|
||||
|
||||
def generate_puml():
|
||||
with lock:
|
||||
lines = ["@startuml", "skinparam responseMessageBelowArrow true", "participant user"]
|
||||
for e in events:
|
||||
d = e.get("data", {})
|
||||
frm = d.get("from", "?")
|
||||
to = d.get("to", "?")
|
||||
subj = d.get("subject", "").replace('"', "'")[:60]
|
||||
lines.append(f'"{frm}" -> "{to}": {subj}')
|
||||
lines.append("@enduml")
|
||||
return "\n".join(lines)
|
||||
|
||||
def render_svg(puml):
|
||||
try:
|
||||
p = subprocess.run(["plantuml", "-tsvg", "-pipe"], input=puml.encode(),
|
||||
capture_output=True, timeout=10)
|
||||
return p.stdout.decode() if p.returncode == 0 else f"<pre>Error: {p.stderr.decode()}</pre>"
|
||||
except Exception as ex:
|
||||
return f"<pre>PlantUML error: {ex}</pre>"
|
||||
|
||||
HTML = """<!DOCTYPE html>
|
||||
<html><head><meta charset="utf-8"><title>AnviLLM Trace</title>
|
||||
<style>
|
||||
body{margin:0;display:flex;height:100vh;font-family:monospace}
|
||||
#left{width:25%;overflow:auto;background:#1e1e1e;color:#d4d4d4;padding:8px;box-sizing:border-box;display:flex;flex-direction:column}
|
||||
#right{width:75%;overflow:auto;background:#fff;display:flex;align-items:center;justify-content:center}
|
||||
#svg-container{transform-origin:center;cursor:grab}
|
||||
pre{margin:0;white-space:pre-wrap;font-size:12px;flex:1}
|
||||
button{margin-bottom:8px;padding:4px 8px;cursor:pointer}
|
||||
</style></head><body>
|
||||
<div id="left"><button onclick="reset()">Reset</button><pre id="puml"></pre></div>
|
||||
<div id="right"><div id="svg-container"></div></div>
|
||||
<script>
|
||||
let scale=1;
|
||||
function refresh(){
|
||||
fetch('/data').then(r=>r.json()).then(d=>{
|
||||
document.getElementById('puml').textContent=d.puml;
|
||||
document.getElementById('svg-container').innerHTML=d.svg;
|
||||
});
|
||||
}
|
||||
function reset(){fetch('/reset',{method:'POST'}).then(()=>{scale=1;refresh();});}
|
||||
document.getElementById('right').addEventListener('wheel',e=>{
|
||||
e.preventDefault();
|
||||
scale*=e.deltaY<0?1.1:0.9;
|
||||
document.getElementById('svg-container').style.transform='scale('+scale+')';
|
||||
});
|
||||
setInterval(refresh,2000);
|
||||
refresh();
|
||||
</script></body></html>"""
|
||||
|
||||
class Handler(BaseHTTPRequestHandler):
|
||||
def log_message(self, *a): pass
|
||||
def do_GET(self):
|
||||
if self.path == "/":
|
||||
self.send_response(200)
|
||||
self.send_header("Content-Type", "text/html")
|
||||
self.end_headers()
|
||||
self.wfile.write(HTML.encode())
|
||||
elif self.path == "/data":
|
||||
puml = generate_puml()
|
||||
svg = render_svg(puml)
|
||||
self.send_response(200)
|
||||
self.send_header("Content-Type", "application/json")
|
||||
self.end_headers()
|
||||
self.wfile.write(json.dumps({"puml": puml, "svg": svg}).encode())
|
||||
else:
|
||||
self.send_error(404)
|
||||
def do_POST(self):
|
||||
if self.path == "/reset":
|
||||
with lock:
|
||||
events.clear()
|
||||
self.send_response(200)
|
||||
self.end_headers()
|
||||
else:
|
||||
self.send_error(404)
|
||||
|
||||
def event_reader():
|
||||
os.environ["NAMESPACE"] = NAMESPACE
|
||||
proc = subprocess.Popen(["9p", "read", "agent/events"], stdout=subprocess.PIPE, text=True)
|
||||
for line in proc.stdout:
|
||||
try:
|
||||
ev = json.loads(line.strip())
|
||||
if ev.get("type") in ("UserSend", "BotSend"):
|
||||
with lock:
|
||||
events.append(ev)
|
||||
except json.JSONDecodeError:
|
||||
pass
|
||||
|
||||
if __name__ == "__main__":
|
||||
threading.Thread(target=event_reader, daemon=True).start()
|
||||
print(f"Trace UI: http://localhost:{PORT}")
|
||||
HTTPServer(("", PORT), Handler).serve_forever()
|
||||
Reference in New Issue