Skip to content

How to Adopt Nebuko

Nebuko runs a multi-agent workflow on your issues and pull requests: plan → context → implement → review, with human approval gates, driven entirely from issue comments. This page takes a repository from zero to a first run.

For the underlying concepts first, read architecture.md and workflows.md.


Adding Nebuko to a repository gives you:

  • Orchestration on your own issues and PRs — start a workflow (/nebuko run workflow --name default), or mention an agent directly (@@architect, @@context, @@dev, @@review) from an issue body or comment.
  • Nebuko branding in the thread — every orchestrator comment is prefixed with the theme-adaptive Nebuko logo mark (a <picture> block serving mark-light.svg / mark-dark.svg), exactly as on this project’s own issues.
  • Branded commit author — commits and PR branches created by the implementation step are authored as nebuko <[email protected]>.
  • AI-attribution signature — agent answers end with a short “generated by an AI agent … on behalf of <user>” line. This line is emitted by the agent prompt (best-effort), not enforced by the engine.
  • Coming — nebuko[bot] actor identity — the App’s own avatar and nebuko[bot] actor name on comments. The App installation itself is live; the actor identity is tracked separately.

Nebuko runs as a GitHub App installation by default: short-lived, per-repository installation tokens minted from the OIDC broker. A fine-grained GH_PAT is only a fallback (section 4).


  1. A caller repository on GitHub. Nebuko’s reusable workflow is hosted in the public nebuko/nebuko repository — GitHub requires the called repository to be public for a reusable workflow to be used from another repository. Your own (caller) repository can be public or private.

  2. A runner. GitHub-hosted ubuntu-latest by default; you can point the jobs at your own runners instead (section 6).

  3. The Nebuko App installed on your repository — install it from https://github.com/apps/nebuko/installations/new and grant it only the target repositories. The App requests Contents, Pull requests and Issues read and write — exactly what the engine uses to clone, push branches, open PRs and comment (section 8).

    No GitHub token to create: nebuko init detects the installation and the workflow mints a short-lived installation token from the OIDC broker. If you cannot install the App, PAT mode is the fallback (section 4).

  4. An agent config at .nebuko/agents/agent-config.yaml in your repository — mandatory for now (section 3, step 1).

  5. A provider API key — one of OPENROUTER_API_KEY, COMMANDCODE_API_KEY, OPENCODE_GO_API_KEY, MISTRAL_API_KEY or LOCAL_VLLM_API_TOKEN, matching the api_token_env of the provider in your agent config (section 5).


Adopting Nebuko is three commits’ worth of work, in this order: the agent config, the workflow, then the secrets that config needs.

Section titled “Step 0 — or let nebuko init do steps 1–3 (recommended)”

Install the nebuko binary from the release whose tag you want to pin, then run the interactive setup:

Terminal window
gh release download -R nebuko/nebuko -p 'nebuko-linux-amd64' -O ~/.local/bin/nebuko
chmod +x ~/.local/bin/nebuko
nebuko init

nebuko init asks for the target repository (or organization), where the secrets should live (repository level by default, or organization level), the LLM provider and the runner labels, then:

  1. generates .nebuko/agents/agent-config.yaml from the chosen provider’s template at the pinned tag;
  2. generates .github/workflows/nebuko.yaml, pinned to that tag and passing only the secrets the provider needs;
  3. opens a branch and a pull request with those two files — no secret value is ever committed;
  4. asks for each secret value with hidden input and stores it with gh secret set at the level you chose (--org <name> skips the prompt).

Secrets already stored at the chosen level (the provider key, or the PAT fallback GH_PAT) are not touched: init reports OPENROUTER_API_KEY already setup in acme [ skipping, use --force to update value ] and does not ask for their values. This is what makes it safe to run init for a second repository against organization-level secrets. Pass --force to rewrite them.

App detection. Before writing anything, init probes the Nebuko App installation for the target repository: an installation means App mode — no PAT prompt — while a 404 (without --pat) makes init stop and print the App install link (https://github.com/apps/nebuko/installations/new); pass --pat (alias --assume-pat) to select the PAT fallback, which prompts for GH_PAT (fallback mode only). In App mode init writes the caller workflow; the workflow reads the broker endpoint from the optional NEBUKO_BROKER_URL repository variable and falls back to the published broker when it is unset. See nebuko-init.md.

It uses your own gh authentication to create the branch, the pull request and the secrets. init needs the GitHub CLI (gh) on your PATH — it stops with an install hint before doing anything else when it is missing. Pushing .github/workflows/* needs the workflow scope — init checks for it up front, before asking for any secret value, and tells you to run gh auth refresh -s workflow when it is missing. Once the setup succeeds it prints the next steps: review and merge the pull request, and comment @@dev Hello Nebuko! on an issue in the target repository.

Release assets: nebuko-linux-amd64, nebuko-macos-arm64, nebuko-macos-amd64, nebuko-windows-amd64.exe.

Flags for scripted installs: --repo, --org, --provider, --runs-on, --pin-ref, --template-dir, --non-interactive (secret values from NEBUKO_SECRET_<NAME>), --force. Re-running init on an initialized repository asks before overwriting the generated files, and refuses in --non-interactive mode unless --force is given; secrets that already exist at the chosen level are skipped (with a note) unless --force is given.

The manual steps below remain the fallback — and the reference for exactly what init generates.

Step 1 — Create your agent config (mandatory)

Section titled “Step 1 — Create your agent config (mandatory)”

Every adopting repository must provide its own .nebuko/agents/agent-config.yaml. This is mandatory for now: it keeps the choice of providers, models, prompts and tools on your side rather than inherited from whatever Nebuko ships. Either write one (section 5, configuration.md) or copy a shipped profile from defaults/agents/ and adapt it:

ProfileProviderSecret it reads (api_token_env)
agent-config-openrouter.yamlOpenRouterOPENROUTER_API_KEY
agent-config-commandcode.yamlCommandCodeCOMMANDCODE_API_KEY
agent-config-opencode.yamlOpenCode GoOPENCODE_GO_API_KEY
agent-config-mistral.yamlMistralMISTRAL_API_KEY
Terminal window
mkdir -p .nebuko/agents
curl -fsSL -o .nebuko/agents/agent-config.yaml \
https://raw.githubusercontent.com/nebuko/nebuko/v0.0.2/defaults/agents/agent-config-openrouter.yaml

Commit the file. Note every api_token_env it declares (one per provider): each one is a secret you must pass in step 2 and create in step 3.

Create .github/workflows/nebuko.yaml in your repository, point agent_config_path at your config, and pass the secrets that config reads (here, the OpenRouter profile):

name: Nebuko
on:
issues:
types: [opened, edited, assigned, closed]
issue_comment:
types: [created]
permissions:
contents: write
pull-requests: write
issues: write
id-token: write
actions: write
jobs:
agent:
uses: nebuko/nebuko/.github/workflows/[email protected]
with:
agent_config_path: ".nebuko/agents/agent-config.yaml"
secrets:
OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }} # = api_token_env in your config

On another provider, replace OPENROUTER_API_KEY with the secret named by your config’s api_token_env (e.g. COMMANDCODE_API_KEY); list one line per provider your config uses.

How to use: only comments authored by a user with OWNER, MEMBER, or COLLABORATOR association start a run; anyone else’s comments are ignored at the caller workflow level. Comments authored by nebuko[bot] pass the caller gate, but the reusable workflow only runs them when they carry a continuation marker (<!-- workflow-continue -->, <!-- bot-trigger: or "function_call"), and never when they carry the terminal marker (anti-loop guard). issues events (issue-body @@nebuko mentions, and the cleanup that runs when an issue is closed) are gated by the reusable workflow as before.

How it works: the installed caller workflow’s agent job carries a job-level if: gate that is evaluated before the reusable workflow is invoked, so a non-member comment never spins up a caller run. The reusable workflow’s own caller check (its CALLER_AUTH input) remains a second, independent layer — defense in depth. The caller workflow’s permissions: block includes id-token: write, matching the reusable workflow’s requirement.

If nebuko init installed your caller workflow earlier, re-run nebuko init to refresh it with the gate (init asks before overwriting).

Under Settings → Secrets and variables → Actions, create a repository secret for every provider key your config’s api_token_env entries reference. A provider whose secret is missing cannot reach its LLM.

No GitHub token is needed in App mode. GH_PAT is a PAT fallback only (section 4): add it when the App is not installed.

Every input is optional; defaults are shown. Pass them under with:.

InputTypeDefaultPurpose
agent_config_pathstringagent-config.yamlPath to your agent config — set it to .nebuko/agents/agent-config.yaml (step 1, section 5). A bare file name is looked up in .nebuko/agents/.
runs_onstringubuntu-latestRunner label used by the jobs (section 6).
circuit_breaker_thresholdnumber25Bot-marker count that trips the circuit breaker (per-issue override: /nebuko update circuit-breaker --value <n>).
pin_commentbooleanfalsePin every agent-step comment for verbatim context injection.
history_comment_countnumber5Most recent comments injected into each agent call.
python_versionstring3.13Python version installed on the runner.
enable_uv_cachebooleantrueEnable the uv dependency cache.
prompt_bodystring""Prompt/body text for programmatic (workflow_call) callers.
checkout_refstring""Explicit ref (SHA, tag, or branch) for the workspace checkout. When empty, the checkout follows the workflow’s own commit if the workflow is checking out its own repo (issue #862), else the event default.
CALLER_AUTHstringonWhen on (default), only OWNER/MEMBER/COLLABORATOR authors may trigger from issues/comments; set to off to allow anyone.
EXTRA_SECRET_1_NAME … EXTRA_SECRET_9_NAMEstring""Env var name materialized from the matching EXTRA_SECRET_n_VALUE secret — use for MCP tokens (section 3.1).
SecretRequiredPurpose
GH_PATfallback onlyFine-grained PAT for git push, issue/PR API calls and comments — PAT fallback only (section 4). With the App installed (and id-token: write granted) the token is minted by the OIDC broker instead.
OPENROUTER_API_KEYoptionalProvider key (the api_token_env referenced by defaults/agents/agent-config-openrouter.yaml).
COMMANDCODE_API_KEYoptionalCommandCode subscription provider.
OPENCODE_GO_API_KEYoptionalOpenCode Go provider.
MISTRAL_API_KEYoptionalMistral provider.
LOCAL_VLLM_API_TOKENoptionalLocal vLLM provider.
BRAVE_API_KEYoptionalBrave Search MCP token (the named alias of the slot pattern in section 3.1).
EXTRA_SECRET_1_VALUE … EXTRA_SECRET_9_VALUEoptionalValues materialized into the env vars named by the EXTRA_SECRET_n_NAME inputs (section 3.1).

Each provider “optional” above becomes required as soon as your agent config’s api_token_env references it.

The workflow also reads one repository variable (not a secret):

VariableRequiredPurpose
NEBUKO_BROKER_URLApp modeOIDC token broker the workflow exchanges its GitHub OIDC token for a short-lived installation token. nebuko init sets it; see section 8.

MCP servers need their own private tokens. You can hand the engine any such token without editing the reusable workflow: add a repository secret, then point one of nine generic slots at the env var name the MCP expects. The with: input carries the name, the secrets: block carries the value:

jobs:
agent:
uses: nebuko/nebuko/.github/workflows/[email protected]
with:
agent_config_path: ".nebuko/agents/agent-config.yaml"
EXTRA_SECRET_1_NAME: "BRAVE_API_KEY" # env var the MCP reads
secrets:
OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}
EXTRA_SECRET_1_VALUE: ${{ secrets.BRAVE_API_KEY }}
  • Slot n pairs EXTRA_SECRET_n_NAME with EXTRA_SECRET_n_VALUE; indices run 1–9.
  • Empty slots are ignored; on a duplicate name the lowest populated slot wins.
  • Names reserved for the runner (GITHUB_*, ACTIONS_*, RUNNER_*, PATH, HOME, GITHUB_TOKEN, GH_TOKEN) are dropped, so a slot cannot clobber engine internals.
  • Precedence: a slot value (written to $GITHUB_ENV) overrides a same-named job-level alias such as the named BRAVE_API_KEY; a name the engine step binds explicitly (LOCAL_VLLM_API_TOKEN) shadows $GITHUB_ENV, so mapping a slot onto it has no effect.

Adding an MCP token is therefore a repository secret plus two lines in your entrypoint — never an edit to nebuko-workflow.yaml. See ADR-0008 for the rationale.


4. Authentication — GitHub App (default)

Section titled “4. Authentication — GitHub App (default)”

Nebuko acts through a GitHub App installation token by default. Nothing long-lived is stored in your repository.

How it works

  1. Your caller workflow grants id-token: write (step 2) and requests a GitHub OIDC token for the job.
  2. The workflow posts that OIDC token to the OIDC broker named by the NEBUKO_BROKER_URL repository variable (nebuko init sets it).
  3. The broker verifies the OIDC token, confirms the Nebuko App is installed on your repository, and returns a short-lived installation token scoped to that repository.
  4. The engine uses that installation token to clone, push branches, open/update PRs and post comments.

The installation token expires with the job, so there is nothing to rotate and no credential to leak from your repository. Permissions, key custody and how to suspend or uninstall the App are in section 8.

PAT mode is selected when the App is not installed on the repository (detection returns 404) or when you pass --assume-pat to nebuko init. Nebuko then acts entirely through the GH_PAT you supply: it clones, pushes branches, opens/updates PRs, and posts comments as the token’s owner. The token is passed to the reusable workflow as a secret and is never written to the checkout.

What the token must be able to do

  • Push branches and read contents in the target repository.
  • Read and write issues, comments, and pull requests.

Security notes

  • Use a fine-grained token, not a classic one. A fine-grained PAT is limited to a single user or organization and can be narrowed to specific repositories.
  • Set an expiration; rotate before it lapses. GitHub removes tokens unused for a year.
  • Organization owners can require approval for fine-grained tokens that can access org resources — expect an approval step on org repositories.
  • Prefer passing the narrowest token that covers the repositories you point Nebuko at.

Branding — the logo-mark comment prefix, the branded nebuko commit author, and the prompt-emitted AI-attribution line (section 1) are the same in both modes. The nebuko[bot] actor identity requires the App and is not available yet.


5. Provider configuration — ready-to-paste configs

Section titled “5. Provider configuration — ready-to-paste configs”

Commit your agent config at .nebuko/agents/agent-config.yaml — mandatory for now (section 3, step 1) — and set agent_config_path to that path. Other config artifacts (prompts, skills, workflows) still resolve through the layered lookup, anchored at the caller’s workspace ($GITHUB_WORKSPACE when set, the working directory otherwise — not the engine checkout): a repo override under .nebuko/ first, then the copy shipped in defaults/. The file is layered providers → llms → agents; the full schema is in configuration.md. The shipped defaults/prompts/ need no local copies, so a config like the one below is the whole minimal setup.

.nebuko/agents/agent-config.yaml:

providers:
openrouter:
api_url: "https://openrouter.ai/api/v1"
api_token_env: "OPENROUTER_API_KEY"
llms:
my-model:
model_path: "openrouter/deepseek/deepseek-chat-v3-0324"
provider: "${providers.openrouter}"
default_temperature: 0.7
agents:
architect:
model: "${llms.my-model}"
prompt_file: "defaults/prompts/architect.md"
tools: ["think", "finish"]
dev:
model: "${llms.my-model}"
prompt_file: "defaults/prompts/dev.md"
tools: ["terminal", "file_editor", "task_tracker", "think", "finish"]
review:
model: "${llms.my-model}"
prompt_file: "defaults/prompts/review.md"
tools: ["think", "finish"]

Wire the matching secret in your entrypoint (section 3):

secrets:
OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}

prompt_file matters for its file name only: Nebuko resolves each agent’s prompt through the same layered lookup (.nebuko/prompts/ override → shipped defaults/prompts/), so the shipped prompt is used unless you add an override with that name.

For a self-hosted vLLM endpoint, inline the URL and point api_token_env at the token secret:

providers:
local_vllm:
api_url: "http://192.168.1.64:31728/v1"
api_token_env: "LOCAL_VLLM_API_TOKEN"
llms:
qwen:
model_path: "hosted_vllm/qwen-3-6-27b"
provider: "${providers.local_vllm}"
default_temperature: 0.7
agents:
architect:
model: "${llms.qwen}"
prompt_file: "defaults/prompts/architect.md"
tools: ["think", "finish"]

Wire the LOCAL_VLLM_API_TOKEN secret in your entrypoint:

secrets:
LOCAL_VLLM_API_TOKEN: ${{ secrets.LOCAL_VLLM_API_TOKEN }}

api_url may also be the name of an environment variable holding the URL (resolved at config-load time, mirroring api_token_env) — e.g. api_url: "LOCAL_VLLM_API_URL". That form only helps when the variable is actually present on the runner, so inline the URL unless your runner sets it.

CommandCode / subscription providers. See subscription-plans.md for the provider stanza and a working agent-config-commandcode.yaml example.


The workflow runs on GitHub-hosted runners by default. Select a runner with the runs_on input:

with:
runs_on: "ubuntu-latest" # default — GitHub-hosted
# runs_on: "self-hosted" # your own runner
# runs_on: "[self-hosted, linux, x64]" # label combination
  • GitHub-hosted (ubuntu-latest) — nothing to install; the workflow sets up Python and uv itself. Billed by GitHub per the repository’s plan.
  • Self-hosted — use the default self-hosted label or a combination such as [self-hosted, linux, x64] (see GitHub’s runner labels). Runners are consumer-managed: you own their availability, patching, and network access to any private vLLM endpoint.

The entrypoint above pins the release tag @v0.0.2 (the default version). Pin a release tag for reproducibility, a full commit SHA for the strongest guarantee, or @main to track unreleased changes:

uses: nebuko/nebuko/.github/workflows/[email protected]
# or: .../nebuko-workflow.yaml@<full-commit-sha>
# or: .../nebuko-workflow.yaml@main # latest, unreleased

To upgrade, bump the tag in your entrypoint and re-fetch your agent config profile from the same tag if you started from a shipped one.

GitHub Actions does not follow redirects for reusable workflows: if the called repository or workflow path is ever renamed, callers pinned to the old name fail. Pinning to an immutable ref is what keeps an upgrade deliberate.


Permissions. The App requests only what the engine uses — the same three repository permissions as the caller workflow’s permissions: block:

PermissionLevelUsed for
Contentsread and writeclone the repository, push branches
Pull requestsread and writeopen and update PRs
Issuesread and writeread triggers, post comments, persist WORKFLOW_STATE

Nothing else: no administration, no packages, no members, no workflows. The workflow additionally requests id-token: write from GitHub — that is the OIDC identity token, not a repository permission, and it is what lets the job ask the broker for an installation token.

Where the key lives. The App’s private key stays with the App owner and the broker — it is never in your repository, your secrets or your workflow. Your repository holds only the NEBUKO_BROKER_URL variable (a URL, not a credential). The installation token the broker returns is short-lived and expires with the job, so there is nothing to rotate and no long-lived credential to leak.

Review, suspend, uninstall.

  • Organization repositories: Settings → Third-party Access → GitHub Apps → Configure.
  • Personal repositories: Settings → Integrations → Applications → Installed GitHub Apps → Configure.
  • Change repository access at any time from the same screen — the App only sees the repositories you granted, and removing one revokes its access on the next token request.
  • Suspend is a temporary block: the App keeps its installation and settings, but every token request fails until you unsuspend it. Nebuko runs stop working while suspended.
  • Uninstall is permanent: the installation and its settings are removed. To use Nebuko again you reinstall the App and re-run nebuko init.

If you cannot install an App at all (policy, or a repository the App is not installed on), use the PAT fallback in section 4 and keep that token narrow and expiring.