Runner Model
Execution layers
| Layer | Where it runs | What it does |
|---|---|---|
| Detect job | GitHub-hosted runner | Thin event parsing: trigger type, actor, agent (if labeled), issue/PR number; pool-selects invoker |
| Dispatch job | GitHub-hosted runner (invoker/<handle> env) |
Persona injection, Claude Code Action, status card, counter update, audit log |
| Claude inference | Anthropic infrastructure | Language model processing; called by the Claude Code Action via OAuth token |
The GitHub runner checks out the Hall repo, assembles the CLAUDE.md context file from the base contract and agent persona, and runs anthropics/claude-code-action@v1. The action drives the agentic loop: calling Claude, executing bash/file tools, and committing results — all on the runner.
GitHub-hosted runners
Runners are ephemeral VMs managed by GitHub. They spin up on workflow trigger, execute all steps, and are destroyed. No org member maintains infrastructure.
What runs on the runner:
- GitHub App token creation (actions/create-github-app-token@v1)
- Composite action steps: authorize, counter, status-card, memory, dispatch, post-dispatch, cleanup
- Shell scripts in scripts/ (yq config reads, context injection, cache operations)
- The Claude Code Action agentic loop (bash, file r/w, git operations on the checked-out target repo)
Composite action model
All orchestration logic lives in actions/ as GitHub composite actions. The dispatch workflows (invoke.yml, hall-ci-loop.yml, hall-cleanup.yml) call these actions. This separation means:
- Orchestration logic is versioned and reusable
- Target repos require no local configuration — the Hall repo is the single source of logic
- Individual action steps can be tested or replaced independently
Concurrency controls
Each dispatch job declares:
concurrency:
group: hall-{agent}-{issue-number}
cancel-in-progress: false
This ensures at most one active dispatch per agent per issue at any time. Re-dispatches queue behind the running job rather than cancelling it.
Invoker pool and environment selection
Each dispatch runs in the environment of the pool-selected invoker (invoker/<handle>). Pool selection happens in the detect job via scripts/detect-invoke-context.js, which:
- Lists all
invoker/*environments via the GitHub Environments API - Reads
HALL_USAGE_COUNTandHALL_WEEKLY_CAPfor each - Filters out members at or over cap
- Sorts by
HALL_USAGE_COUNTascending - Outputs the least-used member as
invoker
The dispatch job then declares environment: invoker/<handle> dynamically. This gives access to that environment's CLAUDE_CODE_OAUTH_TOKEN secret.
If the pool is exhausted (all members at cap), the invoker output is empty, the notify-queued job fires, and the dispatch job is skipped.
Persona injection
At dispatch time, the workflow assembles the agent's operating context:
- Read
agents/automaton_base.mdfrom the Hall repo (checked out in the workflow) - Read
roster/{agent}.mdfrom the Hall repo — the agent's character sheet - Concatenate base contract + persona → write to
CLAUDE.mdin the workspace root - Pass task context as the
promptinput to the Claude Code Action
CLAUDE.md is never committed. The runner is ephemeral — it exists only for the duration of the dispatch job. The base contract (automaton_base.md) explicitly prohibits the agent from committing the file.
State persistence
The runner is ephemeral, but task state persists between runs via:
- Actions Cache: per-task working memory (
hall-task-{repo}-{pr}). Keyed by PR so multiple concurrent tasks on different PRs never collide. 7-day TTL; deleted on PR close byhall-cleanup.yml. - Environment variables (
HALL_USAGE_COUNT,HALL_WEEKLY_CAP): invoker usage tracking. Written by the workflow via the GitHub API after each successful dispatch. - Actions Artifacts: immutable invocation audit logs (
hall-log-{agent}-{issue}-{run_id}) - GitHub issue/PR thread: permanent human-readable task history; serves as fallback context if cache expires
agents.ymlandroster/*.md: live catalog and persona state, version-controlled in the Hall repo
Tradeoffs
| Tradeoff | Consequence |
|---|---|
| GitHub-hosted runners only | No persistent environment; target repo must be checked out |
| App private key in repo secrets | Visible to repo admins — see secrets-model.md |
| Cache as working memory | 7-day expiry; agent reconstructs from issue thread on miss |
Dynamic environment: expression |
GitHub evaluates this at job start; the invoker environment must exist before the first dispatch |
| Pool-based token model | No dedicated per-agent token; any invoker's token can run any agent |