Skip to content
This repository was archived by the owner on May 15, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@ AGENT_ROUTER_WORKSPACE_ROOT=~/Projects
AGENT_ROUTER_WEBHOOK_SECRET=change-me
AGENT_ROUTER_RUNNER_TOKEN=change-me-too
AGENT_ROUTER_SETUP_SECRET=change-me-three
AGENT_ROUTER_HUMA_AGENT=main
GITHUB_APP_ID=
GITHUB_PRIVATE_KEY_B64=
36 changes: 36 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# AgentRouter

GitHub App router for explicit SummonAgent commands across Fischer's personal and HuntDynamics repositories.

## Run

```bash
pnpm install
pnpm verify
pnpm dev
pnpm runner --agent codex
pnpm runner --agent codex --execute --once
```

## Architecture

- `src/server/` starts the HTTP webhook, setup callback, and runner API.
- `src/github/` parses GitHub events, manifests, and checks actor permissions.
- `src/store/` persists jobs in SQLite.
- `src/runner/` claims jobs for Codex, Claude, or Huma local lanes.

## Project Rules

- Triggering must stay explicit: `@SummonAgent codex`, `@SummonAgent claude`, or `@SummonAgent huma`.
- Raw `@codex`, `@claude`, and `@huma` mentions are ignored because Codex/Claude/Huma are not separate GitHub Apps.
- Whoever opened the PR owns the fix. PR jobs must stay on the matching fixed lane branch:
- `codex/workspace`
- `claude/workspace`
- `huma/workspace`
- Never act on bot-authored events.
- Keep repository allowlisting enabled before wiring real agent execution.
- Do not store GitHub private keys, webhook secrets, runner tokens, or agent API keys in git.

## Definition Of Done

Changes are complete when `pnpm verify` passes and any GitHub App behavior has been tested with a signed webhook fixture or a live test repository.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ Copy `.env.example` to `.env` and set:
- `AGENT_ROUTER_SETUP_SECRET`
- `GITHUB_APP_ID`
- `GITHUB_PRIVATE_KEY_B64`
- `AGENT_ROUTER_HUMA_AGENT` optionally selects the OpenClaw agent id for the Huma lane. It defaults to `main`.

`GITHUB_PRIVATE_KEY_B64` should be the GitHub App private key PEM encoded as base64.

Expand Down Expand Up @@ -104,6 +105,7 @@ For a `@SummonAgent codex` PR job on `fschrhunt/AgentRouter`, that maps to `/Vol

- No agent runs unless a comment or review contains `@SummonAgent` plus `codex`, `claude`, or `huma`.
- Raw `@codex`, `@claude`, and `@huma` mentions are ignored.
- Codex, Claude, and Huma are local runner lanes behind this one GitHub App. They are not separate GitHub Apps.
- Bot users are ignored.
- Senders must have `write`, `maintain`, or `admin` repository permission.
- Repositories can be allowlisted in `agent-router.config.json`.
Expand All @@ -127,6 +129,8 @@ When a valid mention is accepted, AgentRouter stores a queued job. Runners claim

For pull requests, jobs include the PR head branch and base branch. The router only queues a PR job when the PR head branch matches the mentioned agent's fixed workspace branch in the same repository.

The Huma lane runs through OpenClaw with `openclaw agent --agent ${AGENT_ROUTER_HUMA_AGENT:-main} --message ...`.

## Development

```bash
Expand Down
9 changes: 8 additions & 1 deletion src/runner/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,14 @@ function runAgent(agent: AgentName, target: WorktreeTarget, prompt: string): voi
return;
}

throw new Error("Huma execution is not wired yet; job was not run.");
const humaAgent = process.env.AGENT_ROUTER_HUMA_AGENT ?? "main";
runStreaming("openclaw", [
"agent",
"--agent",
humaAgent,
"--message",
prompt,
], target.worktreeDir);
}

function runText(command: string, args: string[], cwd: string, allowFailure = false): string {
Expand Down
8 changes: 4 additions & 4 deletions src/server/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ export function evaluatePullRequestRouting(
allowed: false,
reason: "fork head repository",
message: [
`Blocked @${trigger.agent} job: I only push fixes to branches in \`${trigger.repository}\`.`,
`Blocked SummonAgent ${trigger.agent} job: I only push fixes to branches in \`${trigger.repository}\`.`,
`This PR head is \`${pr.headRepository}:${pr.headBranch}\`, so I will not create a new branch or push to a fork.`,
].join("\n\n"),
};
Expand All @@ -90,7 +90,7 @@ export function evaluatePullRequestRouting(
allowed: false,
reason: "unexpected head branch",
message: [
`Blocked @${trigger.agent} job: I only push fixes to the fixed \`${expectedBranch}\` lane branch for @${trigger.agent}.`,
`Blocked SummonAgent ${trigger.agent} job: I only push fixes to the fixed \`${expectedBranch}\` lane branch for that lane.`,
`This PR head is \`${pr.headBranch}\`. I will not create a new branch or move work onto a different branch.`,
].join("\n\n"),
};
Expand All @@ -101,10 +101,10 @@ export function evaluatePullRequestRouting(

function queuedMessage(jobId: number, trigger: TriggerRequest): string {
if (!trigger.isPullRequest) {
return `Queued @${trigger.agent} job #${jobId}.`;
return `Queued SummonAgent ${trigger.agent} job #${jobId}.`;
}
return [
`Queued @${trigger.agent} job #${jobId}.`,
`Queued SummonAgent ${trigger.agent} job #${jobId}.`,
`Branch policy: fixes will use \`${trigger.pullRequestHeadBranch}\` directly. No new branch will be created.`,
].join("\n\n");
}
Loading