GitHub Source
The github source adapter polls issues from a GitHub repository and maps them
to Apiary Cells for routing and dispatch. Pull requests are not ingested:
GitHub's issues endpoint also returns PRs (every PR is an issue in the API), but
the adapter skips them — PRs are implementation artifacts, not work items.
Configuration
sources:
- id: my-repo
type: github
config:
repo: my-org/my-repo
api_key: ${GITHUB_TOKEN}
poll_interval: 120s
filters:
states: [open]
labels: [ai-ready]
config fields
| Field | Required | Default | Description |
|---|---|---|---|
repo |
yes | — | Repository in owner/repo format |
api_key |
no | — | GitHub personal access token (see permissions below) |
base_url |
no | https://api.github.com |
API base URL for GHES |
filters
| Field | Description |
|---|---|
states |
Filter by issue state: open or closed |
labels |
Only poll issues that have all of these labels |
What the adapter does
| Operation | GitHub API call | When |
|---|---|---|
| Poll | GET /repos/{owner}/{repo}/issues?state=open&sort=updated&direction=desc |
Every poll_interval |
| Acknowledge | POST /repos/{owner}/{repo}/issues/{number}/labels (adds in-progress) |
When settings.state_lock: true |
| WriteResult | POST /repos/{owner}/{repo}/issues/{number}/comments |
When settings.result_comment: true |
| SetState | PATCH /repos/{owner}/{repo}/issues/{number} (sets state) |
Via workflow.on_complete.set_state |
| AddLabels | PATCH /repos/{owner}/{repo}/issues/{number} (replaces labels) |
Via workflow.on_complete.add_labels |
GitHub's /issues endpoint also returns pull requests, but the adapter filters
them out during polling — only plain issues become cells (always
Type: "issue").
Token permissions
The api_key is a GitHub personal access token. Without it, the adapter falls
back to unauthenticated requests (60 req/h — insufficient for regular polling).
Classic PAT
Scope repo (for private repos).
Fine-grained PAT
| Permission | Level | Why |
|---|---|---|
Issues |
Read & Write | Poll, update state, add labels, post comments |
Metadata |
Read (auto-granted) | Read repo metadata, list labels |
Per-agent source identity (source_token, source_email, source_name)
Each agent can use its own GitHub account for issue operations and git commits:
agents:
- id: engineer
description: "Implements tasks"
model: claude-sonnet-4-6
source_token: ${GITHUB_TOKEN_ENGINEER}
source_email: engineer@company.com
source_name: Engineer Bot
- id: reviewer
description: "Reviews code"
model: claude-sonnet-4-6
source_token: ${GITHUB_TOKEN_REVIEWER}
source_email: reviewer@company.com
source_name: Reviewer Bot
source_token
When set, the adapter's write operations use this token instead of the
source-level api_key:
- Acknowledge — adds in-progress label
- WriteResult — posts comment with run output
- SetState — closes/re-opens issue via on_complete.set_state
- AddLabels — adds labels via on_complete.add_labels
Poll always uses the source-level api_key — one account reads all issues.
source_email / source_name
These are injected as git environment variables in the runner subprocess:
- GIT_AUTHOR_NAME / GIT_COMMITTER_NAME
- GIT_AUTHOR_EMAIL / GIT_COMMITTER_EMAIL
Ensures commits use the correct author identity instead of a shared system user.
Per-agent concurrency (max_workers)
Each agent has its own semaphore — one agent's long-running tasks don't starve another:
agents:
- id: engineer
description: "Implements tasks"
soul_file: .apiary/agents/engineer.md
model: claude-sonnet-4-6
max_workers: 3
- id: reviewer
description: "Reviews code"
soul_file: .apiary/agents/reviewer.md
model: claude-sonnet-4-6
max_workers: 2
Default is 1 if not set. Polls are serialized (one source at a time) regardless
of settings.concurrency.
Routing (workflows)
A workflow's trigger decides which tasks it handles. Triggers are evaluated in
priority ascending order (lower first); the first match wins. The match fields
below live under trigger.match.
match fields
| Field | Type | Description |
|---|---|---|
source |
string |
Only match tasks from this source ID |
labels |
[string] |
Task must have all of these labels (case-insensitive) |
exclude_labels |
[string] |
Task must NOT have any of these labels |
exclude_label_prefix |
string |
Task must not have a label starting with this prefix |
states |
[string] |
Only match tasks whose state is in this list |
types |
[string] |
Only match tasks whose type is in this list (GitHub tasks are always "issue") |
title_regex |
string |
Task title must match this Go regexp |
priority |
[string] |
Only match tasks whose priority is in this list |
on_complete fields
| Field | Type | Description |
|---|---|---|
set_state |
string |
Transition the task to this state after a successful run |
add_labels |
[string] |
Add these labels to the task after a successful run |
Example: trigger by label
workflows:
- id: engineer-implement
trigger:
priority: 20
match:
source: my-repo
labels: [agent:engineer]
steps:
- id: run
agent: engineer
on_complete:
set_state: closed
The trigger matches any task carrying the agent:engineer label and dispatches
it to the engineer agent, closing the issue when the run succeeds.
Environment variables
.env auto-load
When loading a config file, Apiary automatically reads .env from the same
directory and calls os.Setenv for each entry. Already-set env vars (e.g.
from shell export) take priority.
# .env (next to apiary.yaml)
GITHUB_TOKEN=ghp_abc123
GITHUB_TOKEN_ENGINEER=ghp_def456
GITHUB_TOKEN_REVIEWER=ghp_ghi789
No manual sourcing needed — Config.Load() handles it.
Full list
| Variable | Used By |
|---|---|
GITHUB_TOKEN |
Source adapter — poll + fallback for write operations |
GITHUB_TOKEN_<AGENT> |
Agent's source_token — overrides source token for writes |
GIT_AUTHOR_NAME |
Runner — set from agent.source_name |
GIT_AUTHOR_EMAIL |
Runner — set from agent.source_email |
GIT_COMMITTER_NAME |
Runner — same as GIT_AUTHOR_NAME |
GIT_COMMITTER_EMAIL |
Runner — same as GIT_AUTHOR_EMAIL |
Setting up locally
- Create a fine-grained PAT
- Set it in
.envor export:export GITHUB_TOKEN=ghp_... - Run:
apiary run --config apiary.yaml