Stigmergy in Practice: GitHub Agents Without an Orchestrator
A concrete walkthrough of stigmergy applied to GitHub, where issues, PRs, labels, and comments become the signals that autonomous agents sense, act on, and leave behind.
Someone asked me last week how stigmergy would actually work in a real system. Not the theory. The practice. What does it look like when agents coordinate through the environment instead of talking to each other?
The answer was already in front of us. GitHub is a stigmergy environment. We just hadn't framed it that way.
You're already doing stigmergy
Think about how a well-run open source project works. A user files an issue. A maintainer triages it, adds a label. Someone sees the label, picks up the work, opens a PR. A reviewer sees the PR, leaves comments. The PR gets approved, merged, deployed.
No one sent a direct message saying "hey reviewer, please look at PR #247." The PR's existence in the environment was the signal. The reviewer's habit of watching for open PRs was the sensor. The review was the deposit.
That's indirect coordination through a shared medium. That's stigmergy. Every GitHub repository with issues and pull requests is already a stigmergic system. We just had humans doing the sensing.
Now replace the humans with agents.
Mapping the concepts
GitHub's native primitives map directly to Mandible's stigmergy model:
| GitHub Primitive | Mandible Concept | Role |
|---|---|---|
| Repository | Environment | The shared substrate agents read from and write to |
| Issue | Signal | A work request with type, payload, and metadata |
| Pull Request | Signal | A code contribution deposited by a coding agent |
| Label | Signal modifier | Type and concentration metadata (bug, priority:high) |
| Comment | Signal | Context or feedback deposited by agents or humans |
| Review | Signal | Approval or change request from a review agent |
| Merge | Withdrawal | Removing a processed signal from the active environment |
The environment already exists. The signal format already exists. We just need colonies with the right sensors.
The flow
Here's a bug report flowing through four agent colonies. No colony knows about any other colony. They coordinate entirely through GitHub.
1. Signal enters the environment
A user opens an issue: "Login button unresponsive on mobile Safari." This is an unprocessed signal: no labels, no assignee, sitting in the environment waiting to be sensed.
2. Triage colony: sense → act → deposit
Sensor pattern: issues:opened where labels is empty. The triage colony reads the issue body, classifies it as bug with priority:high, and deposits labels. The issue now carries stronger metadata. A richer signal.
3. Coder colony: sense → claim → act → deposit
Sensor pattern: issues:labeled:bug where unclaimed. The coder colony claims the issue by self-assigning, reads the linked codebase, writes a fix, and opens a pull request. The PR is a brand new signal in the environment, linked to the issue, carrying a diff as its payload.
4. Review colony: sense → act → deposit
Sensor pattern: pr:opened where unreviewed. The review colony reads the diff, checks for correctness, security, and style, and deposits a review. If it requests changes, the coder colony senses that signal and iterates. The feedback loop runs through the environment, not through a message bus.
5. Merge colony: sense → act → withdraw
Sensor pattern: pr:approved where checks:passing. The merge colony squash-merges the PR and closes the issue. Signals are withdrawn from the active environment. The work is done.
Five steps. Four colonies. Zero direct references between them.
The code
Here's how you'd configure these colonies with Mandible:
import {
colony, createRuntime, GitHubEnvironment,
withClaudeCode, withStructuredOutput, withBash
} from 'mandible';
const env = new GitHubEnvironment({
owner: 'acme',
repo: 'frontend',
token: process.env.GITHUB_TOKEN,
});
const triage = colony('triage')
.in(env)
.sense('issues:opened', { unclaimed: true, filter: { labels: [] } })
.do(withStructuredOutput({
model: 'claude-sonnet-4-6',
schema: { labels: 'string[]', priority: 'number', summary: 'string' },
prompt: 'Classify this issue. Assign labels and priority 1-5.',
}))
.claim('exclusive')
.build();
const coder = colony('coder')
.in(env)
.sense('issues:labeled', { unclaimed: true, filter: { labels: ['bug'] } })
.do(withClaudeCode({
systemPrompt: `You are a senior engineer. Read the issue,
find the root cause, write a fix, and open a PR.`,
allowedTools: ['Read', 'Write', 'Bash', 'Grep', 'Glob'],
}))
.concurrency(3)
.claim('lease', 300_000)
.build();
const reviewer = colony('reviewer')
.in(env)
.sense('pulls:opened', { unclaimed: true })
.do(withClaudeCode({
systemPrompt: `You are a code reviewer. Check for correctness,
security issues, and style. Leave a thorough review.`,
allowedTools: ['Read', 'Grep'],
}))
.claim('exclusive')
.build();
const merger = colony('merger')
.in(env)
.sense('pulls:approved', { filter: { checks: 'passing' } })
.do(withBash({
command: 'gh pr merge {{signal.payload.number}} --squash --delete-branch',
}))
.claim('exclusive')
.build();
No colony references any other colony. Add a fifth (say, a docs colony that senses merged PRs and updates documentation) without touching a single line of existing configuration. Remove the reviewer colony and a human maintainer fills the gap without knowing anything changed. The environment doesn't care who deposits a review, only that one appears.
What emerges
This isn't a different syntax for the same pipeline. The architecture gives you properties that are hard to retrofit onto orchestrated systems:
- Self-healing: the coder colony crashes mid-PR, the lease expires, the issue becomes unclaimed, another instance picks it up. No supervisor needed.
- Human-agent interchangeability: a human maintainer can triage an issue, review a PR, or merge by hand. To every other colony, it's just another signal. Humans and agents are peers in the environment.
- Observability is the environment: every action is a GitHub issue, comment, label, PR, or review. Your audit trail is
gh issue listandgh pr list. No separate logging infrastructure. - Natural backpressure: issues piling up? Signal concentration rises. Spin up more coder instances. They self-organize around available work with zero configuration changes.
The pattern underneath
So, how does stigmergy work in practice? It's all around you.
The coordination pattern that scales open source to millions of contributors - file a signal, let whoever cares pick it up, leave your work where others can sense it - this is the same pattern that can scale autonomous agents. The environment was always the orchestrator. Mandible gives agents the antennae. And the interesting part to me isn't that agents can now participate in the loop. It's that the loop doesn't have to change to let them in.
Check out the GitHub repo to get started, or explore Mandible Cloud to deploy colonies.