diff --git a/scripts/test_node.py b/scripts/test_node.py index 70fb64e..834377c 100644 --- a/scripts/test_node.py +++ b/scripts/test_node.py @@ -1,7 +1,6 @@ import sys import os -sys.path.append("/app/agent-node") -from agent_node.skills.shell import ShellSkill +from agent_node.skills.shell_bridge import ShellSkill class DummySync: def get_session_dir(self, sid): @@ -21,6 +20,7 @@ payload_json = "pwd\n" session_id = "s1" trace_id = "tr1" + timeout_ms = 5000 def on_event(msg): print("ON_EVENT:", msg) diff --git a/swarm_framework/SKILL.md b/swarm_framework/SKILL.md index a9f3501..b7e8509 100644 --- a/swarm_framework/SKILL.md +++ b/swarm_framework/SKILL.md @@ -56,7 +56,8 @@ ```json { "request_id": "req_123", - "target_agent": "agent_1", + "sender": "master", + "target_agent": "orchestrator", "prompt": "Your instruction here", "response_file": "swarm_framework/comms/responses/res_123.json" } @@ -70,3 +71,14 @@ "output": "... captured terminal output ..." } ``` + +## Communication Boundaries Rules + +To maintain clear boundaries and prevent Master Agent overload, the following rules are enforced by `orchestrator.py`: + +1. **Master Agent** can only be triggered by the **Orchestrator Agent**. +2. **Sub-agents** can trigger **Sub-agents** and **Orchestrator Agent**. +3. **Master Agent** can trigger **Orchestrator Agent** ONLY. + +Ensure all request files populate the `"sender"` field! + diff --git a/swarm_framework/agents/master/SKILL.md b/swarm_framework/agents/master/SKILL.md index 7765482..3a226ab 100644 --- a/swarm_framework/agents/master/SKILL.md +++ b/swarm_framework/agents/master/SKILL.md @@ -15,7 +15,8 @@ { "request_id": "req_123", "type": "prompt", - "target_agent": "agent_1", + "sender": "master", + "target_agent": "orchestrator", "prompt": "Your instructions to the agent here.", "response_file": "../../comms/responses/res_123.json" } @@ -23,11 +24,19 @@ * `request_id`: A unique identifier for the request. * `type`: Must be `"prompt"` for sending instructions. -* `target_agent`: The name of the target window (e.g., `"agent_1"`, `"agent_2"`). +* `sender`: MUST be `"master"`! +* `target_agent`: MUST be `"orchestrator"`! * `prompt`: The text message or command you want the agent to execute. * `response_file`: The relative path where the Orchestrator will write the response. +## Communication Boundaries Rules + +As the **Master Agent**, you are subject to strict communication boundaries enforced by the system: +1. **You can only trigger the Orchestrator Agent** (`target_agent`: `"orchestrator"`). You cannot target sub-agents directly. +2. **You can only be triggered by the Orchestrator Agent**. Do not accept tasks from sub-agents. + ### 2. Reading a Response + The background Orchestrator will pick up your request, send it to the agent, and write the response to the specified `response_file`. You should poll for the existence of the response file and read its content. diff --git a/swarm_framework/agents/orchestrator/SKILL.md b/swarm_framework/agents/orchestrator/SKILL.md new file mode 100644 index 0000000..be2b415 --- /dev/null +++ b/swarm_framework/agents/orchestrator/SKILL.md @@ -0,0 +1,19 @@ +**Objective:** To manage the swarm of agents to accomplish high-level goals. + +**Workflow:** +1. **Read Tasks**: Read `tasks.md` in the parent directory to see pending tasks. +2. **Dispatch Tasks**: Write JSON request files to `../../comms/requests/` to assign tasks to sub-agents (`agent_1`, `agent_2`, etc.) or Master Agent. +3. **Monitor Responses**: Read JSON response files from `../../comms/responses/` to check results. +4. **Handle Interactions**: If a sub-agent asks a question, read it from the response and send a follow-up prompt! + +**Communication Boundaries Rules:** +You are the central hub for communication: +* You can trigger **Sub-agents** (`agent_X`) and the **Master Agent** (`master`). +* You are the **ONLY** agent allowed to trigger the **Master Agent**! + +**JSON Schema Reminder:** +Always include `"sender": "orchestrator"` in your requests! + +**JSON Schema Details:** +See `SKILL.md` in the parent directory for full details. + diff --git a/swarm_framework/bootstrap.sh b/swarm_framework/bootstrap.sh index 58ec526..d2eaa76 100755 --- a/swarm_framework/bootstrap.sh +++ b/swarm_framework/bootstrap.sh @@ -47,28 +47,36 @@ sleep 5 tmux send-keys -t $SESSION_NAME:master "Please read the file 'SKILL.md' in your current directory to understand how to control the swarm." C-m -# Create Orchestrator window -tmux new-window -t $SESSION_NAME -n orchestrator -c "$SCRIPT_DIR" -# Run orchestrator with unbuffered output and redirection -tmux send-keys -t $SESSION_NAME:orchestrator "python3 -u $ORCHESTRATOR_PATH > orchestrator.log 2>&1" C-m +# Create Physical Dispatcher window +tmux new-window -t $SESSION_NAME -n dispatcher -c "$SCRIPT_DIR" +tmux send-keys -t $SESSION_NAME:dispatcher "python3 -u $ORCHESTRATOR_PATH > orchestrator.log 2>&1" C-m -# Create Grid window for sub-agents -tmux new-window -t $SESSION_NAME -n grid -c "$SCRIPT_DIR" +# Create Grid window for all agents (Orchestrator + Sub-agents) +mkdir -p "$SCRIPT_DIR/agents/orchestrator" +tmux new-window -t $SESSION_NAME -n grid -c "$SCRIPT_DIR/agents/orchestrator" + +# Enable pane borders and titles +tmux set-window-option -t $SESSION_NAME:grid pane-border-status top +tmux set-window-option -t $SESSION_NAME:grid pane-border-format " #P: #{pane_title} " + +# Pane 0: Orchestrator Brain +tmux select-pane -t $SESSION_NAME:grid.0 -T "Orchestrator Brain" +tmux send-keys -t $SESSION_NAME:grid.0 "$CLI_PATH -cli=true" C-m # Add sub-agents to Grid window as panes for i in $(seq 1 $N); do mkdir -p "$SCRIPT_DIR/agents/agent_$i" - if [ $i -eq 1 ]; then - tmux send-keys -t $SESSION_NAME:grid "$CLI_PATH -cli=true" C-m - else - tmux split-window -t $SESSION_NAME:grid -c "$SCRIPT_DIR/agents/agent_$i" - tmux send-keys -t $SESSION_NAME:grid "$CLI_PATH -cli=true" C-m - fi + tmux split-window -t $SESSION_NAME:grid -c "$SCRIPT_DIR/agents/agent_$i" + tmux select-layout -t $SESSION_NAME:grid tiled + tmux select-pane -t $SESSION_NAME:grid.$i -T "Sub Agent $i" + tmux send-keys -t $SESSION_NAME:grid.$i "$CLI_PATH -cli=true" C-m done + # Arrange Grid window in tiles tmux select-layout -t $SESSION_NAME:grid tiled + # Wait for agents to settle and handle any prompts echo "Aligning agents..." python3 "$SCRIPT_DIR/../tmp/swarm/scripts/swarm_auto_aligner.py" @@ -83,9 +91,9 @@ echo "To use the Grid of Sub-Agents, run:" echo " tmux attach -t $SESSION_NAME:grid" echo "" -echo "To see the Orchestrator logs, attach to its window:" -echo " tmux attach -t $SESSION_NAME:orchestrator" -echo "Or check the file: $SCRIPT_DIR/orchestrator.log" +echo "To see the Orchestrator, it is Pane 0 in the Grid window." +echo "Or check the log file: $SCRIPT_DIR/orchestrator.log" + echo "" echo "To view Master and Grid independently in two terminals:" echo " Run this in the second terminal to open the grid automatically:" diff --git a/swarm_framework/orchestrator.py b/swarm_framework/orchestrator.py index 785b9d8..ab9546f 100644 --- a/swarm_framework/orchestrator.py +++ b/swarm_framework/orchestrator.py @@ -63,12 +63,25 @@ result = subprocess.run(cap_cmd, shell=True, capture_output=True, text=True, check=True) current_output = result.stdout + # Check for manual prompts in Orchestrator + if agent == "grid.0" or agent == "orchestrator": + lines = current_output.split('\n') + last_snippet = "\n".join(lines[-5:]) if len(lines) > 5 else current_output + + if "1." in last_snippet and "2." in last_snippet: + if not state.get("prompted"): + print(f"Detected prompt in Orchestrator.") + notify_master_prompt("orchestrator", current_output) + state["prompted"] = True + print(f"Debug [check_hanging]: Agent: {agent}, current_output len: {len(current_output)}, prev_output len: {len(state['last_output'])}") if current_output != state["last_output"]: state["last_output"] = current_output state["last_output_time"] = now + state["prompted"] = False # Reset prompt flag on change print(f"Debug [check_hanging]: Agent: {agent}, output changed.") + else: # No change silence_duration = now - state["last_output_time"] @@ -99,6 +112,21 @@ except Exception as e: print(f"Failed to notify master: {e}") +def notify_master_prompt(agent, last_output): + print(f"Notifying master about prompt in agent {agent}...") + lines = last_output.split('\n') + last_snippet = "\n".join(lines[-5:]) if len(lines) > 5 else last_output + + message = f"\n[System] Orchestrator Brain needs input.\nPrompt snippet:\n---\n{last_snippet}\n---\nPlease reply to continue." + + try: + escaped_msg = message.replace('"', '\\"') + cmd = f"tmux send-keys -t {SESSION_NAME}:master \"{escaped_msg}\" C-m" + subprocess.run(cmd, shell=True, check=True) + except Exception as e: + print(f"Failed to notify master: {e}") + + def process_request(file_path): try: with open(file_path, 'r') as f: @@ -107,10 +135,33 @@ req_id = req.get("request_id") req_type = req.get("type", "prompt") target = req.get("target_agent") + sender = req.get("sender") resp_file = req.get("response_file") + # Enforce boundary rules + if target == "master": + if sender != "orchestrator": + raise ValueError("Master agent can only be triggered by orchestrator agent!") + + if sender == "master": + if target != "orchestrator": + raise ValueError("Master agent can trigger orchestrator only!") + + if sender and (sender.startswith("grid.") or sender.startswith("agent_")): + # Subagent can trigger subagent or orchestrator + if target != "orchestrator" and not (target.startswith("grid.") or target.startswith("agent_")): + raise ValueError("Subagents can only trigger subagent or orchestrator!") + + if not sender: + print(f"Warning: Request {req_id} missing 'sender' field. Boundary rules not fully enforced.") + # Mapping for Grid layout + if target == "orchestrator": + target = "grid.0" + print(f"Mapped orchestrator to grid.0") + if target.startswith("agent_"): + try: agent_num = int(target.split("_")[1]) target = f"grid.{agent_num - 1}"