Building a multi-agent system from scratch: 50 lines of bash + git | Andros Fenollosa
The concept of orchestrating agents is gradually spreading among developers. Claude has planned, or you may already be able to use (depending on when you're reading this), the ability to launch several autonomous agents working in parallel and in a coordinated manner. This is great, who doesn't like working faster? However, you can build this functionality yourself. Of course, only if you don't mind burning a lot of tokens at once.
The elements I use are simple:
A TODO.org file or similar: to define tasks, their status, and dependencies.
A run script: to launch agents with the same prompt and limit the number of parallel agents.
A local or remote Git repository: so agents can synchronize code with each other.
If we were in the context of the Scrum framework:
Project owner: that's TODO.org, since it defines the tasks, their description, status, and dependencies.
Developers: those are the autonomous agents or the run script.
Scrum Master: that's the Git repository, since it ensures all agents respect the rules and timing.
TODO.org: the coordination hub
The power of using Org Mode is that we can have tasks in plain text with properties and statuses. It is also readable by both humans and machines.
* TODO Task 1<br>:PROPERTIES:<br>:ID: task-1<br>:END:
Description of task 1.
* TODO Task 2<br>:PROPERTIES:<br>:ID: task-2<br>:BLOCKER: task-1<br>:END:
Description of task 2, which depends on task 1.<br>Each task goes one below the other, with a title, an ID, and a detailed description. If a task depends on another, we add a :BLOCKER: property with the ID of the blocking task. The status of each task is indicated with the prefix TODO, IN-PROGRESS, or DONE. This way, agents can read the file, identify which tasks are available to execute (those with TODO status and no pending blockers), and mark their progress.
Of course, you can use any format or platform (Jira, Trello, etc.). The only requirement is that agents can interact with it programmatically, to read tasks and mark their status.
Run script and agent prompt
Defining tasks is useless if we don't have agents to execute them.
We create a run_agents.sh script with the following content:
#!/bin/bash
MAX_JOBS=16<br>AGENT_NUM=0
while true; do<br>while (( $(jobs -rp | wc -l) >= MAX_JOBS )); do<br>sleep 1<br>done
git pull --rebase origin main 2>/dev/null
if ! grep -q "^\* TODO" TODO.org; then<br>wait<br>break<br>fi
COMMIT=$(git rev-parse --short=6 HEAD)<br>AGENT_NUM=$((AGENT_NUM + 1))<br>LOGFILE="agent_logs/agent_${COMMIT}_${AGENT_NUM}.log"
claude --dangerously-skip-permissions -p "Your agent number is: $AGENT_NUM. $(cat AGENT_PROMPT.md)" &> "$LOGFILE" &<br>done<br>As you can see, there is a MAX_JOBS variable that limits the number of agents running in parallel. Before launching each one, the script does a git pull to read the updated state of TODO.org: if there are no more TODO tasks, it waits for the running agents to finish with wait and exits. Each agent will execute the same prompt, found in AGENT_PROMPT.md, and will save its output to a log file with the current commit hash and its agent number ($AGENT_NUM).
I used claude --dangerously-skip-permissions -p to launch the agents. The --dangerously-skip-permissions flag is necessary so each agent can operate autonomously without stopping to ask for confirmation on every action. You can use whichever model or client you prefer, but make sure the agents can run commands without human intervention.
The -p mode is single-shot: when Claude finishes generating its response, the process dies on its own. Even so, it's important to explicitly tell the agent to run exit 0 via bash when no TODO tasks remain. This prevents the agent from getting stuck in an indefinite wait loop when all tasks are in IN-PROGRESS or DONE.
AGENT_PROMPT.md is a document, or Skill, that defines agent behavior, collaboration rules, how to choose tasks, how to block tasks, etc. An example could be the following:
# Synchronization Prompt for Autonomous Agents
## Context
You are an autonomous agent working in collaboration with other agents on a shared Git repository. Coordination is done through a shared `TODO.org` file that acts as the single source of truth for task status.
## Work environment
Each agent works in its own isolated Docker instance. At startup, bring up your container from the project's `compose.yaml`:
```bash<br>AGENT_PORT=$((8080 + YOUR_AGENT_NUMBER)) docker compose -p agent-YOUR_AGENT_NUMBER up -d<br>```
You never share an instance with another agent. Each one has its own independent execution environment. The remote Git repository is the only synchronization point between agents: all coordination is done exclusively via `git pull` / `git push`.
## TODO.org file format
The `TODO.org` file contains tasks in the following format:
```org<br>* TODO Task name<br>:PROPERTIES:<br>:ID: task-identifier<br>:END:
Detailed description of what needs to be done.
* TODO Another task with a...