# Robot Dev Team Project # File: config/routes.yaml # Description: Routing configuration for GitLab webhook events. # License: MIT # SPDX-License-Identifier: MIT # Copyright (c) 2025 MCKNLY LLC # Sample routing configuration for webhook event handling. # See docs/ROUTES.md for detailed schema and additional examples. # # IMPORTANT: Replace "your-username" with your GitLab username in each route's # author field. The author field accepts either a single string or a list of # usernames if you want one route to be triggerable by multiple operators, # e.g. author: ["alice", "bob"]. Matching is case-insensitive in both forms. # For persistent local overrides, copy this file to config/routes.local.yaml, # customise it, and set ROUTE_CONFIG_PATH in .env: # ROUTE_CONFIG_PATH=config/routes.local.yaml # # Access modes: # - readonly: Agent runs in read-only project mount (default). The agent # receives filesystem errors if it attempts to write files. # - readwrite: Agent runs in read-write project mount. Use for routes # that need to modify code. # # Timeout overrides (optional per-route, falls back to global env vars): # max_wall_clock_seconds: Hard upper-bound run duration (default: 7200) # max_inactivity_seconds: Watchdog resets on stdout output only (default: 900). # Stderr is tracked for diagnostics but deliberately # does not stay the timeout, so a retry loop (which # produces only stderr) cannot hold the queue open. # Raise this for a harness that goes quiet for long # stretches, e.g. a local model during a long prefill. # # Agent dispatch order (optional per-route): # randomize: true -> shuffle the agents list per matched event so the same # agent does not always run first. Only meaningful on # multi-agent routes (e.g. issue-triage). Defaults to # false. See docs/ROUTES.md for details. routes: # Issue-assignment routes - triggered when an agent is assigned to an issue. # GitLab's /assign quick action on an existing issue sends an Issue Hook with # action=update; populating the assignee at issue-creation time sends an Issue # Hook with action=open. Matching both (action: ["open", "update"]) means an # issue created with an agent already assigned dispatches that agent's # read-write assign_work route immediately, instead of first going through # readonly triage/review. These routes detect agent usernames in the assignees # list and grant read-write access for the agent to perform work. # # Placement matters: resolve_match returns the FIRST matching route, so these # must sit ABOVE issue-triage (and any issue-update fallback). Otherwise a # create-with-assignee event would fall through to the readonly issue-triage # review. This is deliberately either/or -- an issue created with an agent # assigned skips triage and goes straight to work. # # Assign-on-creation can be disabled with ENABLE_ASSIGN_ON_ISSUE_CREATION=false # (default true), which reverts creation events to triage. The /assign-on-an- # existing-issue path (action=update) is unaffected by that toggle. - name: assign-issue-claude access: readwrite match: event: "Issue Hook" action: ["open", "update"] author: "your-username" assignees: ["claude"] agents: - agent: "claude" task: "assign_work" prompt: "assign_work.txt" options: command: "claude" args: ["--model", "${CLAUDE_MODEL}", "--dangerously-skip-permissions", "-p"] - name: assign-issue-gemini access: readwrite match: event: "Issue Hook" action: ["open", "update"] author: "your-username" assignees: ["gemini"] agents: - agent: "gemini" task: "assign_work" prompt: "assign_work.txt" options: command: "agy" args: ["--model", "${GEMINI_MODEL}", "--dangerously-skip-permissions", "-p", "${PROMPT}"] - name: assign-issue-codex access: readwrite match: event: "Issue Hook" action: ["open", "update"] author: "your-username" assignees: ["codex"] agents: - agent: "codex" task: "assign_work" prompt: "assign_work.txt" options: command: "codex" args: ["--model", "${CODEX_MODEL}", "exec", "--yolo", "--skip-git-repo-check"] # OpenCode issue-assignment route (optional, disabled by default). Read-write # like the other assignment routes. Strip the leading `#` (column 0) to enable. # - name: assign-issue-opencode-kimi # access: readwrite # match: # event: "Issue Hook" # action: ["open", "update"] # author: "your-username" # assignees: ["opencode-kimi"] # agents: # - agent: "opencode-kimi" # task: "assign_work" # prompt: "assign_work.txt" # options: # command: "opencode" # args: ["run", "--model", "${OPENCODE_KIMI_MODEL}", "--format", "json", "--auto"] # Goose issue-assignment route (optional, disabled by default). Read-write like # the other assignment routes. Strip the leading `#` (column 0) to enable. # # Note the trigger and the agent are deliberately different strings: `assignees` # matches the GitLab *username* from the webhook payload (`gemma`), while `agent` # is the slug that resolves GOOSE_GEMMA_MODEL + GOOSE_GEMMA_AGENT_* (`goose-gemma`). # Same split as the opencode-kimi / `kimi` pair -- the harness prefix stays out of # the GitLab account name. # # A local model can go quiet for a long prefill, so the inactivity watchdog is # raised above the 900s default here to keep a run from being killed mid-think. # - name: assign-issue-goose-gemma # access: readwrite # max_inactivity_seconds: 1800 # match: # event: "Issue Hook" # action: ["open", "update"] # author: "your-username" # assignees: ["gemma"] # agents: # - agent: "goose-gemma" # task: "assign_work" # prompt: "assign_work.txt" # options: # command: "goose" # args: ["run", "--no-session", "--model", "${GOOSE_GEMMA_MODEL}", "-i", "-"] # Grok Build issue-assignment route (optional, disabled by default). Read-write # like the other assignment routes. Strip the leading `#` (column 0) to enable. # # The inactivity watchdog is raised above the 900s default because Grok's plain # output format prints the reply in one go at the end of the run: a long stretch # of tool calls produces no stdout to reset the timer. Switch the route to # `--output-format streaming-json` instead if you would rather have continuous # token events (at the cost of JSON-shaped run-logs). # - name: assign-issue-grok # access: readwrite # max_inactivity_seconds: 1800 # match: # event: "Issue Hook" # action: ["open", "update"] # author: "your-username" # assignees: ["grok"] # agents: # - agent: "grok" # task: "assign_work" # prompt: "assign_work.txt" # options: # command: "grok" # args: ["--model", "${GROK_MODEL}", "--no-auto-update", "--always-approve", "-p", "${PROMPT}"] - name: issue-triage access: readonly randomize: true # Uncomment together with the goose or grok entries below -- both go quiet for # long stretches (a local model during prefill; grok's `plain` format, which # prints nothing until the run ends) and the watchdog only resets on stdout. # Applies to every agent on this route, which is harmless: it only widens the # silence they are allowed before being killed. # max_inactivity_seconds: 1800 match: event: "Issue Hook" action: "open" author: "your-username" agents: - agent: "claude" task: "issue_review" prompt: "issue_review.txt" options: command: "claude" args: ["--model", "${CLAUDE_MODEL}", "--dangerously-skip-permissions", "-p"] - agent: "gemini" task: "issue_review" prompt: "issue_review.txt" options: command: "agy" args: ["--model", "${GEMINI_MODEL}", "--dangerously-skip-permissions", "-p", "${PROMPT}"] - agent: "codex" task: "issue_review" prompt: "issue_review.txt" options: command: "codex" args: ["--model", "${CODEX_MODEL}", "exec", "--yolo", "--skip-git-repo-check"] # OpenCode issue-review entry (optional, disabled by default). The shared # `opencode` binary backs each opencode- agent. To enable, strip the # leading `#` from the lines below (it sits at column 0 so removing it restores # the correct YAML indentation). See docs/ROUTES.md. # - agent: "opencode-kimi" # task: "issue_review" # prompt: "issue_review.txt" # options: # command: "opencode" # args: ["run", "--model", "${OPENCODE_KIMI_MODEL}", "--format", "json", "--auto"] # Goose issue-review entry (optional, disabled by default). Strip the leading # `#` (column 0) from the lines below to enable. Like OpenCode, the single # `goose` binary backs many logical agents, so each is named after its model # (`goose-gemma` here) and resolves its own GOOSE_GEMMA_* env vars. Add another # by cloning these blocks with a new suffix, e.g. `goose-qwen`. # # Goose takes the prompt on stdin (`-i -`) and `--no-session` keeps webhook runs # from accumulating session state. Provider and extensions come from the host # config dir mounted in docker-compose.yml -- do NOT add `--provider` here: # routes only substitute ${VAR} in the argument right after `--model`, so any # other placeholder would reach the CLI as a literal string. # See docs/ADDING_AN_AGENT.md. # - agent: "goose-gemma" # task: "issue_review" # prompt: "issue_review.txt" # options: # command: "goose" # args: ["run", "--no-session", "--model", "${GOOSE_GEMMA_MODEL}", "-i", "-"] # Grok Build issue-review entry (optional, disabled by default). Strip the # leading `#` (column 0) from the lines below to enable. See docs/ROUTES.md. # # Grok takes the prompt as an argv value via the ${PROMPT} placeholder, the same # way `agy` does -- it does NOT read stdin. Its `--prompt-file` flag accepts # /dev/stdin and looks like the stdin answer (it even works under a shell # redirect), but the dispatcher hands the child a pipe and closes the write end # once the prompt is written; grok then re-opens fd 0 *by path*, and reopening a # pipe with no writers left fails with ENXIO. Do not "fix" this back to # --prompt-file. Auth comes from the host config dir mounted in # docker-compose.yml (~/.grok/auth.json, written by `grok login`). # # `--no-auto-update` is not about pinning a version -- scripts/install-grok.sh # still fetches latest at every container start. It stops the *running agent* from # updating itself: grok's updater stages into ~/.grok/downloads and repoints # ~/.grok/bin/{grok,agent}, and ~/.grok is bind-mounted from the host, so a # container-side update would drop a ~150 MB binary in the operator's home and # flip the symlink their host grok resolves through. # # Uncomment `max_inactivity_seconds` on the route above together with this entry: # in the default `plain` format grok emits no stdout until the run ends, and the # watchdog only resets on stdout. The route-level value applies to every agent on # the route, which is harmless -- it only widens the silence they are allowed. # - agent: "grok" # task: "issue_review" # prompt: "issue_review.txt" # options: # command: "grok" # args: ["--model", "${GROK_MODEL}", "--no-auto-update", "--always-approve", "-p", "${PROMPT}"] - name: default-merge-request access: readonly randomize: true # Uncomment together with the goose or grok entries below; see the note on # issue-triage above. # max_inactivity_seconds: 1800 match: event: "Merge Request Hook" action: "open" agents: - agent: "claude" task: "merge_request_review" prompt: "merge_request_review.txt" options: command: "claude" args: ["--model", "${CLAUDE_MODEL}", "--dangerously-skip-permissions", "-p"] - agent: "gemini" task: "merge_request_review" prompt: "merge_request_review.txt" options: command: "agy" args: ["--model", "${GEMINI_MODEL}", "--dangerously-skip-permissions", "-p", "${PROMPT}"] - agent: "codex" task: "merge_request_review" prompt: "merge_request_review.txt" options: command: "codex" args: ["--model", "${CODEX_MODEL}", "exec", "--yolo", "--skip-git-repo-check"] # OpenCode MR-review entry (optional, disabled by default). Strip the leading # `#` (column 0) from the lines below to enable. # - agent: "opencode-kimi" # task: "merge_request_review" # prompt: "merge_request_review.txt" # options: # command: "opencode" # args: ["run", "--model", "${OPENCODE_KIMI_MODEL}", "--format", "json", "--auto"] # Goose MR-review entry (optional, disabled by default). Strip the leading `#` # (column 0) from the lines below to enable. # - agent: "goose-gemma" # task: "merge_request_review" # prompt: "merge_request_review.txt" # options: # command: "goose" # args: ["run", "--no-session", "--model", "${GOOSE_GEMMA_MODEL}", "-i", "-"] # Grok Build MR-review entry (optional, disabled by default). Strip the leading # `#` (column 0) from the lines below to enable. # - agent: "grok" # task: "merge_request_review" # prompt: "merge_request_review.txt" # options: # command: "grok" # args: ["--model", "${GROK_MODEL}", "--no-auto-update", "--always-approve", "-p", "${PROMPT}"] # MR assignment routes - same as issue assignments but for merge requests. - name: assign-mr-claude access: readwrite match: event: "Merge Request Hook" action: "update" author: "your-username" assignees: ["claude"] agents: - agent: "claude" task: "assign_work" prompt: "assign_work.txt" options: command: "claude" args: ["--model", "${CLAUDE_MODEL}", "--dangerously-skip-permissions", "-p"] - name: assign-mr-gemini access: readwrite match: event: "Merge Request Hook" action: "update" author: "your-username" assignees: ["gemini"] agents: - agent: "gemini" task: "assign_work" prompt: "assign_work.txt" options: command: "agy" args: ["--model", "${GEMINI_MODEL}", "--dangerously-skip-permissions", "-p", "${PROMPT}"] - name: assign-mr-codex access: readwrite match: event: "Merge Request Hook" action: "update" author: "your-username" assignees: ["codex"] agents: - agent: "codex" task: "assign_work" prompt: "assign_work.txt" options: command: "codex" args: ["--model", "${CODEX_MODEL}", "exec", "--yolo", "--skip-git-repo-check"] # OpenCode MR-assignment route (optional, disabled by default). Strip the # leading `#` (column 0) to enable. # - name: assign-mr-opencode-kimi # access: readwrite # match: # event: "Merge Request Hook" # action: "update" # author: "your-username" # assignees: ["opencode-kimi"] # agents: # - agent: "opencode-kimi" # task: "assign_work" # prompt: "assign_work.txt" # options: # command: "opencode" # args: ["run", "--model", "${OPENCODE_KIMI_MODEL}", "--format", "json", "--auto"] # Goose MR-assignment route (optional, disabled by default). Strip the leading # `#` (column 0) to enable. # - name: assign-mr-goose-gemma # access: readwrite # max_inactivity_seconds: 1800 # match: # event: "Merge Request Hook" # action: "update" # author: "your-username" # assignees: ["gemma"] # agents: # - agent: "goose-gemma" # task: "assign_work" # prompt: "assign_work.txt" # options: # command: "goose" # args: ["run", "--no-session", "--model", "${GOOSE_GEMMA_MODEL}", "-i", "-"] # Grok Build MR-assignment route (optional, disabled by default). Strip the # leading `#` (column 0) to enable. # - name: assign-mr-grok # access: readwrite # max_inactivity_seconds: 1800 # match: # event: "Merge Request Hook" # action: "update" # author: "your-username" # assignees: ["grok"] # agents: # - agent: "grok" # task: "assign_work" # prompt: "assign_work.txt" # options: # command: "grok" # args: ["--model", "${GROK_MODEL}", "--no-auto-update", "--always-approve", "-p", "${PROMPT}"] # Regular mention routes - triggered by @agent mentions without /assign. - name: mention-claude access: readonly match: event: "Note Hook" action: "create" author: "your-username" mentions: ["claude"] agents: - agent: "claude" task: "note_followup" prompt: "note_followup.txt" options: command: "claude" args: ["--model", "${CLAUDE_MODEL}", "--dangerously-skip-permissions", "-p"] - name: mention-gemini access: readonly match: event: "Note Hook" action: "create" author: "your-username" mentions: ["gemini"] agents: - agent: "gemini" task: "note_followup" prompt: "note_followup.txt" options: command: "agy" args: ["--model", "${GEMINI_MODEL}", "--dangerously-skip-permissions", "-p", "${PROMPT}"] - name: mention-codex access: readonly match: event: "Note Hook" action: "create" author: "your-username" mentions: ["codex"] agents: - agent: "codex" task: "note_followup" prompt: "note_followup.txt" options: command: "codex" args: ["--model", "${CODEX_MODEL}", "exec", "--yolo", "--skip-git-repo-check"] # OpenCode mention route (optional, disabled by default). Read-only like the # other mention routes. Strip the leading `#` (column 0) to enable. # - name: mention-opencode-kimi # access: readonly # match: # event: "Note Hook" # action: "create" # author: "your-username" # mentions: ["opencode-kimi"] # agents: # - agent: "opencode-kimi" # task: "note_followup" # prompt: "note_followup.txt" # options: # command: "opencode" # args: ["run", "--model", "${OPENCODE_KIMI_MODEL}", "--format", "json", "--auto"] # Goose mention route (optional, disabled by default). Read-only like the other # mention routes. Strip the leading `#` (column 0) to enable. # - name: mention-goose-gemma # access: readonly # max_inactivity_seconds: 1800 # match: # event: "Note Hook" # action: "create" # author: "your-username" # mentions: ["gemma"] # agents: # - agent: "goose-gemma" # task: "note_followup" # prompt: "note_followup.txt" # options: # command: "goose" # args: ["run", "--no-session", "--model", "${GOOSE_GEMMA_MODEL}", "-i", "-"] # Grok Build mention route (optional, disabled by default). Read-only like the # other mention routes. Strip the leading `#` (column 0) to enable. # # Unlike opencode-* and goose-*, one `grok` binary backs exactly one logical # agent, so the GitLab username and the agent slug are the same string (`grok`) # and the env vars are the plain GROK_AGENT_* / GROK_MODEL forms. # - name: mention-grok # access: readonly # max_inactivity_seconds: 1800 # match: # event: "Note Hook" # action: "create" # author: "your-username" # mentions: ["grok"] # agents: # - agent: "grok" # task: "note_followup" # prompt: "note_followup.txt" # options: # command: "grok" # args: ["--model", "${GROK_MODEL}", "--no-auto-update", "--always-approve", "-p", "${PROMPT}"] # Pi mention route (optional, disabled by default). Read-only like the other # mention routes. Strip the leading `#` (column 0) to enable. # # Like goose-*, the GitLab username and the agent slug are deliberately different: # `mentions` matches the GitLab *username* from the webhook payload (`nemotron`), # while `agent` is the slug that resolves PI_NEMOTRON_MODEL + PI_NEMOTRON_AGENT_* # (`pi-nemotron`). The harness prefix stays out of the GitLab account name. # # Pi takes the prompt on stdin: there is no ${PROMPT} token in args, so the # dispatcher pipes the rendered prompt to the child (print mode `-p` merges piped # stdin into the initial prompt). `--no-session` keeps webhook runs from writing # session state into the bind-mounted ~/.pi/agent, and `--no-approve` stops an # untrusted repo/branch from opting itself into executable Pi extensions. Provider # auth (the OpenRouter key) comes from the host config dir mounted in # docker-compose.yml (~/.pi/agent/auth.json), not from an env key. Only the arg # right after `--model` is substituted, so keep ${PI_NEMOTRON_MODEL} in that slot. # A 550B model can prefill silently, so the inactivity watchdog is raised above the # 900s default (the watchdog resets on stdout only). # - name: mention-pi-nemotron # access: readonly # max_inactivity_seconds: 1800 # match: # event: "Note Hook" # action: "create" # author: "your-username" # mentions: ["nemotron"] # agents: # - agent: "pi-nemotron" # task: "note_followup" # prompt: "note_followup.txt" # options: # command: "pi" # args: ["-p", "--no-session", "--no-approve", "--model", "${PI_NEMOTRON_MODEL}"]