Building a multi-agent system from scratch: 50 lines of bash and Git

ibobev1 pts0 comments

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...

agents todo task agent tasks done

Related Articles