Skip to content

Reference

Machine-readable contracts used by nebuko: the AGENT_RUN_CONFIG dispatch sentinel, the workflow YAML grammar, the WORKFLOW_STATE v1 JSON schema, the environment-variable catalog, and the bot marker conventions.


AGENT_RUN_CONFIG is a single-line HTML-comment sentinel that carries a compact JSON payload. It is a durable, engine-rendered record of a run’s hand-off: the engine builds it from typed state (the dispatch_next / call_agent control-plane tools) and appends it to the posted comment exactly once. Agents do not emit it themselves (issue #876); the parser below is retained only as a compatibility input for legacy comments. The canonical matching pattern is defined in engine/lib/validate_output.py; all consumers (output building, comment preparation) reuse it from there.

<!-- AGENT_RUN_CONFIG {"dispatch": {"status": "advance"}, "complexity": "medium"} -->

Single line + HTML comment means no multi-line boundary ambiguity and no leak into rendered GitHub comments. A sentinel split across lines never matches.

The payload is validated by the canonical model AgentRunConfig (engine/lib/config/sub_domains/agent_run_config/agent_run_config.py), shared by the validator and the routing helpers. Two invariants hold:

  1. At least one action is required — one of task, dispatch or function_call. An empty payload is invalid.
  2. task and function_call are mutually exclusive — they are two different ways to name a target; combining them is rejected.

task is therefore not required: a dispatch-only sentinel such as {"dispatch": {"status": "advance"}} is valid. See handoff-and-callback.md for the full contract, the callback return address, and the routing-precedence cascade.

The current dispatch contract (see engine/lib/workflow_state/sub_domains/dispatch/dispatch.py):

FieldTypeDescription
statusstringAgent outcome: advance, loop, hold, or terminate. The workflow engine resolves routing from this plus the workflow definition.
task_hintstringOptional suggested next agent (overrides workflow YAML).
complexitystringComplexity for the downstream agent: low, medium, high. When unset, the downstream workflow step’s declared steps[].complexity applies, else medium (issue #787). Auto-adjusts temperature (see configuration.md).
reasoningstringReasoning effort: none, low, medium, xhigh (high is an alias for xhigh). Only applies while thinking is enabled in the LLM config.
modelstringOptional LLM model override for the downstream agent.
temperaturefloatSampling override.
top_pfloatNucleus sampling override.
top_kintTop-K selection override.
min_pfloatMinimum-P sampling override.
presence_penaltyfloatPresence penalty override.
repetition_penaltyfloatRepetition penalty override.
workflowstringOptional workflow name override (file in workflows/).
stepstringOptional step name override within the workflow.
branchstringOptional condition key for conditional routing.
parent_workflowstringParent workflow name when entering a sub-workflow.
parent_workflow_stepstringParent step that triggered the sub-workflow.
skillsstringOptional skill(s) for the downstream agent.
pinboolWhen true, pin this step’s output comment(s) so downstream agents receive their full body verbatim (mirrors the PIN_COMMENT workflow input / env var).
reasonstringOptional free-text explanation of the dispatch decision.

Defaults: status: "advance", reasoning: "none", everything else empty/null. An unset complexity falls back to the workflow step’s declared steps[].complexity, else "medium" (issue #787).

Legacy Flat Fields (Backward Compatibility)

Section titled “Legacy Flat Fields (Backward Compatibility)”

Older sentinels use flat fields instead of dispatch (see the README schema table): task (the target agent role; required only when neither dispatch nor function_call is present), skills (string[]), complexity, reasoning, round (dev↔review callback counter), temperature, top_p, top_k, repetition_penalty, model, workflow, step, branch.

Examples:

<!-- AGENT_RUN_CONFIG {"task": "review", "complexity": "medium"} -->
<!-- AGENT_RUN_CONFIG {"task": "dev", "round": 2} -->
<!-- AGENT_RUN_CONFIG {"task": "context", "workflow": "default", "step": "context"} -->
<!-- AGENT_RUN_CONFIG {"task": "dev", "complexity": "low", "top_p": 0.1, "top_k": 10, "repetition_penalty": 1.2} -->

Rule: human users trigger agents with @@mention; only agents may use AGENT_RUN_CONFIG. Bot agents cannot use @@mention to avoid self-triggering loops.


Workflows are first-class YAML definitions in workflows/ (local) or fetched from the nebuko repo (zero-copy). See workflows.md. The graph-level invariants below are checked offline by python -m engine lint.

KeyScopeTypeRequiredDescription
namefilestringyesWorkflow name (must match the filename).
descriptionfilestringnoHuman-readable description.
inputsfilelistnoTyped parameters this workflow requires — its function signature (issue #813). Each entry is a name/kind/required spec. See workflows.md.
inputs[].nameinputstringyesParameter name, referenced downstream through input.<name>.
inputs[].kindinputexec|stryesexec = a deterministic provider command materialized by the engine; str = a constant.
inputs[].requiredinputboolnoWhen true (default), a failed/absent value fails the consuming step deterministically, before the agent runs.
outputsfilestring[]noThe workflow’s results, each naming a step whose posted comment is that result (issue #756; a bare string coerces to a one-element list). Supersedes the pre-#756 opaque <call-step>.<name> return values.
stepsfilelistyesOrdered list of steps. The first step is the workflow’s entry point.
steps[].namestepstringyesUnique step name.
steps[].agentstepstringone of agent/workflow/repo/scriptAgent role executing the step.
steps[].workflowstepstring/pathone of agent/workflow/repo/scriptSub-workflow to mount instead of an agent (name or path like tmp_impl/workflow.yaml).
steps[].scriptstepobjectone of agent/workflow/repo/scriptDeterministic, non-agent script execution (see below).
steps[].script.commandscriptstringyes (with script)Executable to run (uv, git, or a repo-relative script such as scripts/test.sh). Absolute paths and .. segments are rejected.
steps[].script.argsscriptstring[]noArguments passed to command (argv list, no shell interpolation). Entries survive to the child process verbatim, spaces and quotes included.
steps[].script.working_dirscriptstringnoRepo-relative working directory for the command; defaults to the checkout root. Absolute paths and .. segments are rejected.
steps[].script.envscriptmap<string,string>noExtra environment variables for the command; these override inherited variables with the same key.
steps[].script.timeout_minutesscriptintnoExecution timeout in minutes (≥ 1; default 30, capped at 60).
steps[].pre_hookstepobjectnoDeterministic command run in the session workspace immediately before the agent (issue #884). Fail-closed: a non-zero exit or timeout skips the agent and fails the step. Agent steps only.
steps[].post_hookstepobjectnoDeterministic command run in the session workspace immediately after the agent attempt (issue #884), even when the agent fails. Fail-open: its failure is recorded but never changes the step outcome or routing. Agent steps only.
steps[].pre_hook|post_hook.pathhookstringyes (with the hook)Repo-relative path of the script to run. Absolute paths and .. segments are rejected.
steps[].pre_hook|post_hook.argshookstring[]noArguments passed to the hook (argv list, no shell interpolation).
steps[].pre_hook|post_hook.timeout_minuteshookintnoExecution timeout in minutes (≥ 1; default 30, capped at 60).
steps[].nextstepstring/nullnoNext step name, or null for terminal.
steps[].conditionssteplistnoConditional routing, evaluated in order.
steps[].conditions[].on_resultconditionstringyesResult key reported by the agent (e.g., approved, rejected, default).
steps[].conditions[].nextconditionstring/nullyesTarget step for that result.
steps[].skillstepentry[]noSkill attachments for this step (files in defaults/skills/). Each entry is a bare name (legacy, means inject) or - inject: [names] / - discover: [names]; the step’s mode wins over the agent’s for a repeated name (issue #955; see agents-and-skills.md).
steps[].promptstepstringnoStanding instruction injected into this step’s agent context (multi-line YAML blocks supported). May interpolate upstream step comments with {{ <step> }} (issue #756). See workflows.md.
steps[].complexitystepstringnoDeclared complexity tier for this step’s agent invocation: low, medium, or high. A dispatch complexity overrides it; otherwise it selects agents.<name>.complexity_overrides.<tier> and the tier temperature floor (issue #787; see configuration.md).
steps[].human_gatestepboolnoSecurity boundary: the engine intercepts every advancing dispatch and holds the workflow until an identity-verified human releases it with /nebuko approve gate / /nebuko reject gate (see workflows.md).
steps[].inputsstepmap or listnoCall-site wiring (issue #813). On a workflow call step the keys are the callee’s declared inputs; on an agent consumer step the values are input.<name> (a workflow parameter) or a bare <step> (that step’s posted comment, issue #756). A list of step names (inputs: [plan, implement]) is shorthand for a map of ref bindings. Each value is one of exec (argv), str (constant), or a bare string meaning a ref.

Minimal valid workflow:

name: my-workflow
description: "A minimal two-step workflow"
trigger:
agents: [architect]
steps:
- name: analyze
agent: architect
next: review
- name: review
agent: review
next: null

Rules:

  • Step names must be unique within the workflow.
  • next / conditions[].next must reference a valid step name or null.
  • Conditions are evaluated in order; include a catch-all (e.g., default) to keep routing deterministic.
  • A step must declare exactly one of agent, workflow, repo, or script.
  • A script step is deterministic and non-agent: it runs script.command with script.args as argv (no shell), posts its output as a comment, then advances. Consecutive script steps run in one engine invocation over the same checkout, so state one step creates is visible to the next. The result is reported as pass (exit 0) or fail (non-zero exit / timeout) so conditions[] can route on it (e.g., on_result: fail → dev). A failing script still advances the workflow in v1 — it does not hold or terminate; the successor (usually an agent) receives the raw failure output.
  • A workflow’s declared inputs/outputs make it a typed function: every declared input must be bound exactly once at each workflow call site, a binding’s kind must match the declaration (a ref is kind-preserving), and every reference must resolve in scope. python -m engine lint checks this offline (rules L8–L10).

Script step example:

steps:
- name: run-tests
script:
command: uv # required (executable or repo-relative script)
args: ["run", "pytest"] # optional argv list
working_dir: src # optional, repo-relative
env: # optional, overrides inherited keys
CI: "true"
timeout_minutes: 15 # optional, default 30
next: review

Typed inputs example — a super-parent pushes concrete commands, a middle workflow forwards them, and the leaf consumes them (block style is canonical; a bare-string value is a ref):

# super-parent.yaml — the composition root supplies the arguments
name: super-parent
steps:
- name: spec-driven
workflow: spec-driven
inputs:
setup_dev_env:
exec: ["nix", "develop", "-c", "true"]
run_test:
exec: ["nix", "develop", "-c", "pytest", "."]
# spec-driven.yaml — declares its parameters and forwards them unchanged
name: spec-driven
inputs:
- name: setup_dev_env
kind: exec
- name: run_test
kind: exec
outputs:
- build
steps:
- name: build
workflow: spec-driven-impl
inputs:
setup_dev_env: input.setup_dev_env # bare string ⇒ ref (kind-preserving)
run_test: input.run_test
# spec-driven-impl.yaml — the leaf consumes; `exec` runs just-in-time here
name: spec-driven-impl
inputs:
- name: setup_dev_env
kind: exec
- name: run_test
kind: exec
steps:
- name: stub_and_implement
agent: dev
inputs:
setup_dev_env: input.setup_dev_env
run_test: input.run_test # materialized immediately before the agent

Forwarding an exec input forwards the command spec, not its output: the engine materializes it exactly once, at the deepest consuming step’s prompt-assembly seam, so freshness is depth-invariant. See ADR-0009.


The workflow engine persists state inside issue comments as a collapsed JSON section between two markers:

<!-- WORKFLOW_STATE_START -->
{ ...state JSON... }
<!-- WORKFLOW_STATE_END -->

Model: engine/lib/workflow_state/workflow_state.py (WorkflowState, Pydantic BaseModel). Every agent step produces a fresh full-snapshot state (no delta updates); the last state found in a comment body is authoritative.

FieldTypeDescription
run_idintMonotonically increasing invocation counter: the Nth state update in this workflow run (issue #821). Carried across the trigger->agent hand-off so a reset is detectable.
schema_versionintPayload shape generation this state was written at (STATE_SCHEMA_VERSION, currently 5). Legacy blobs still carrying the old version key are read as generation 3, with that value migrated into run_id (issue #821). Generation 5 adds thread and the per-step history[].inputs ledger (issue #872); older snapshots deserialize with empty defaults.
workflowstringName of the active workflow.
stepstringCurrent step name.
agentstringAgent role executing the current step.
phasestringHigh-level phase (analysis, planning, execution, …).
historyHistoryEntry[]Summarized records of prior steps (see below).
threadThreadRecord[]The issue’s thread ledger (issue #872): one classified, id-keyed metadata record per issue artifact (issue body, command, engine output, agent output, human feedback). Reconciled against the full comment list between runs; the prompt’s ## Recent thread is rendered from the relevant records. Body-marker bot-ness is anchored to the engine’s own GET /user account (or a genuine bot account), so a forged marker cannot reach the trusted flow.
pinned_comment_idsint[]Comment IDs injected verbatim into the next agent’s context; every other included comment is injected as a summary/excerpt.
current_contextobjectFree-form shared data between agents (pending tasks, constraints, decisions, last_run_status, …).
parent_workflowstring | nullParent workflow name when inside a sub-workflow. Derived view of the second-to-last position frame.
parent_workflow_stepstring | nullParent step that mounted the sub-workflow.
positionStepRef[]The run’s full nested call-stack — flat, outermost-first; the last frame is the step actually executing (issue #638). Engine-owned, carried forward across transitions: entry appends a frame, an intra-workflow move replaces the leaf, return-to-parent pops it (issue #812).
issueIssueInfo | nullStatic issue metadata: number, url, title, author, repo.
pull_requestPullRequestInfo | nullSame shape as issue, for PR contexts.
dispatchDispatch | nullOutcome + runtime preferences of the current agent (see Dispatch Object).
resolutionStepResolution | nullFull context of the step-resolution decision (debugging): resolved role, prompt location, matched rule, applied config, timestamp.
gate_decisionGateDecision | nullLatest engine-owned human-gate decision (issue #648): status (approved/rejected), decided_by, decided_at, feedback, comment_id, and the position hold instance it consumes.
gate_historyGateDecision[]Append-only audit of every gate decision (issue #648).
FieldTypeDescription
stepstringStep name.
agentstringAgent role.
summarystringOne-line summary of what happened.
comment_idsint[]GitHub comment IDs of the step output (multi-part agent messages are posted as several comments; legacy snapshots storing a scalar comment_id are coerced to a list on load).
inject_contextboolWhether the comment should be included in the next agent’s context.
artifactsstring[]Paths to artifacts produced by the step.
pinboolWhether this entry’s comment_ids are injected verbatim (their ids are unioned into pinned_comment_ids during state enrichment).
resultstring | nullReported result (approved, rejected, …).
dispatchDispatch | nullRouting decision emitted by this step (same shape as the top-level Dispatch Object); null for legacy entries that predate the field. Makes the history a replayable movement log.
positionStepRef[]The mount stack recorded for this step (issue #638). After every run history[-1].position equals WorkflowState.position; faithful stacks let the terminal unwind recover the ancestor chain one level at a time (issue #812).
metricsStateMetrics | nullMetrics of this step’s agent invocation (full Metrics payload; issue #792). history[-1].metrics is the single metrics authority.
inputsStepInputRecord[]The step’s materialized deterministic inputs (issue #872): name, binding (exec/str/ref), ok, detail, the resolved value (capped at MAX_VALUE_CHARS), and a truncated flag. Empty for legacy entries.

One metadata-only provenance record per GitHub issue artifact (issue #872) — the unit the reconciler dedups on and the renderer draws from. Bodies are not copied into state; they stay in the thread.

FieldTypeDescription
kindstringissue-body | command | engine-output | agent-output | human-feedback. The engine-output / agent-output kinds are derived only for a bot author; a non-bot author can only be a command or human-feedback.
idintGitHub REST id (issue id or comment id).
urlstringhtml_url permalink.
user_idintAuthor’s GitHub user id.
user_loginstringAuthor’s login.
user_namestringAuthor’s display name (best-effort; REST payloads omit it, so usually empty).
created_atstringISO-8601 creation timestamp.
author_associationstringGitHub author_association (OWNER, MEMBER, …).
trustedboolWhether a human-authored record (command / human-feedback) may be rendered as trusted context (allowlisted association and the run’s trigger author); untrusted records appear only in the flagged unverified appendix.
deletedboolSet when the id vanishes from the fetched comment list (frozen record kept for audit).

Persisted form of one materialized step input (issue #872).

FieldTypeDescription
namestringInput name.
bindingstringBinding kind that produced the value (exec/str/ref).
okboolWhether materialization succeeded.
detailstringFailure detail (empty on success).
valuestringResolved value, truncated to MAX_VALUE_CHARS.
truncatedboolWhether value was capped.
{
"run_id": 1,
"schema_version": 5,
"workflow": "default",
"step": "context",
"agent": "context",
"phase": "analysis",
"history": [
{"step": "analyze", "agent": "architect", "summary": "Issue analyzed, plan requested", "comment_ids": [1029384], "result": "advance", "dispatch": {"status": "advance", "task_hint": "context", "complexity": "high"}, "inputs": [{"name": "diff", "binding": "exec", "ok": true, "detail": "", "value": "diff --git ...", "truncated": false}], "metrics": {"run_id": "1bb4941e-0bbf-432b-b79c-0b408feea07f", "model_name": "default", "agent_role": "architect", "input_tokens": 8365, "output_tokens": 1143, "max_context_window": 0, "cache_hit_rate": 0.44, "cache_hit_tokens": 3680, "reasoning_tokens": 120, "tool_call_latency_ms": 19685.7, "agent_runtime_ms": 19685.7, "nb_tool_calls": 0, "tool_call_error_rate": 0.0, "review_fix_requests": 0}}
],
"thread": [
{"kind": "issue-body", "id": 5794187059, "url": "https://github.com/o/r/issues/501", "user_id": 12345, "user_login": "olivier-norture", "user_name": "", "created_at": "2026-10-01T00:00:00Z", "author_association": "OWNER", "trusted": true, "deleted": false},
{"kind": "agent-output", "id": 1029384, "url": "https://github.com/o/r/issues/501#issuecomment-1029384", "user_id": 99, "user_login": "nebuko[bot]", "user_name": "", "created_at": "2026-10-02T00:00:00Z", "author_association": "NONE", "trusted": false, "deleted": false}
],
"pinned_comment_ids": [],
"current_context": {"last_run_status": "success"},
"parent_workflow": null,
"parent_workflow_step": null,
"issue": {"number": 501, "url": "https://github.com/o/r/issues/501", "title": "write documentation", "author": "olivier-norture", "repo": "o/r"},
"pull_request": null,
"dispatch": {"status": "advance", "complexity": "high"},
"resolution": {"role_name": "context", "matched_rule": "workflow:default/step:context", "timestamp": "2026-09-04T21:05:53Z"}
}

Step Output Fields: Requirements Ledger and Coverage (issue #1257)

Section titled “Step Output Fields: Requirements Ledger and Coverage (issue #1257)”

Two output fields carry the requirements ledger and its plan coverage as markdown tables (see ADR-0016).

How to use. The analyze step writes a requirements field: a table with one row per ledger entry — ID (R<n> requirement, K<n> constraint, NG<n> non-goal, Q<n> quality goal), kind, one atomic checkable statement, a source (short quote or comment link), and an origin (explicit or inferred). Mark inferred rows so the human gate can confirm or remove them; ambiguities become ## Questions entries with the default you proceed with — never stop and wait. The plan step writes a coverage field: one row per ledger ID with covered by (the decision or implementation step that covers it) and verified by (the test name or check). Every R and K must appear; a row may instead be deferred: <reason> (use it only for work genuinely out of scope, with the reason). Each NG entry is checked as “not violated”, and Q entries rank the options in ## Approaches considered.

How it works. The engine parses the IDs from the requirements body of the analyze output and the coverage body of the plan output (src/nebuko/engine/ledger.py). When the plan step’s output.fields declares coverage, the finalize path compares the enforced IDs (R and K only — NG and Q are prompt/critique-checked, never refused) against the covered and deferred rows; a missing ID refuses the finalize in the same turn with coverage missing: R3, K1 (sorted, comma-joined), like the no-report refusal. The check is fail-open: no coverage field, no ledger in run state, or an unparseable ledger (warning only) makes the check a no-op, so default and legacy workflows are unaffected. Metrics emitted: ledger size, number of inferred entries, and coverage refusals per run (LedgerMetricsCollector).


Contract for the fenced ```json block @@context emits inside ### EXTERNAL REFERENCES of its context report. It is an array with one object per reference; the human-readable entries and this block must cover the same references. This block is the access path for tool-less agents (e.g. @@architect) (features.md — External Reference Sharing).

FieldTypeDescription
urlstringThe reference URL.
datestringISO-8601 UTC fetch date.
chunksstring[]Verbatim, unedited excerpts of the page relevant to the issue (≤ ~2,000 tokens per reference, ≈ ≤ 8,000 chars; ≤ ~20K tokens across the whole run).
summarystring1–3 sentences on the article’s relevance/interest to the specific issue or question.
scorenumberFloat in 0.0–1.0: level of interest/relevance to the issue (not source credibility).
[
{
"url": "https://docs.searxng.org/dev/search_api.html",
"date": "2026-09-23T09:25:00Z",
"chunks": ["SearXNG supports querying via a simple HTTP API ..."],
"summary": "Documents the /search endpoint used by @@context; confirms results[].content is snippet-only.",
"score": 0.9
}
]

The block must be valid JSON: no comments, no trailing commas, no prose outside the fence. There is no code model for this contract (prompt-only); this table is the source of truth.


Variables the orchestrator sets/consumes per run:

VariableSet byDescription
TARGET_NUMBERevent_normalizer.pyIssue/PR number the run targets.
TRIGGER_BODYevent_normalizer.pyRaw text of the triggering issue body or comment.
TRIGGER_AUTHORevent_normalizer.pyAuthor of the triggering event.
EVENT_TYPEevent_normalizer.pyGitHub event type (issues, issue_comment, workflow_call, …).
ROLE_NAMEresolve_config / resolve_workflowResolved agent role (architect, context, dev, review, …).
PROMPT_LOCATIONresolve_config / resolve_workflowPath to the resolved prompt file.
WORKFLOW_NAMEresolve_config / resolve_workflowResolved workflow name.
WORKFLOW_STEPresolve_config / resolve_workflowResolved current step name.
WORKFLOW_NEXTresolve_config / resolve_workflowNext step name after the current one.
STEP_PROMPT_FILEresolve_config / resolve_workflowPath to the step’s standing instruction file (steps[].prompt). Empty when the step has no prompt.
CONFIG_COMPLEXITYresolve_configEffective tier for this run (low/medium/high): an explicit dispatch complexity, else the step’s steps[].complexity, else medium. Drives agents.<name>.complexity_overrides.<tier> and the tier temperature floor (issue #787).
RUN_SCRIPTresolve_config"true" when the resolved step is a deterministic script step (no agent runs).
SCRIPT_PATHresolve_configThe step’s script.command verbatim (executable or repo-relative script).
SCRIPT_ARGSresolve_configJSON array of the step’s script.args argv entries (empty array when none); decoded with json.loads at spawn.
SCRIPT_WORKING_DIRresolve_configRepo-relative working directory for the script (from script.working_dir; empty → checkout root).
SCRIPT_ENVresolve_configJSON object of extra environment variables (from script.env; empty object when none), overlaid on the engine environment.
SCRIPT_TIMEOUT_MINUTESresolve_configScript timeout in minutes (empty → default 30).
PARENT_WORKFLOWresolve_config / resolve_workflowParent workflow name when the run is nested in a sub-workflow mount; empty at top level. Derived from the second-to-last position frame.
PARENT_WORKFLOW_STEPresolve_configStep in the parent workflow that mounted a sub-workflow.
WORKFLOW_POSITIONresolve_config / resolve_workflowSingle-line JSON of the run’s full mount stack (WorkflowState.position).
WORKFLOW_POSITION_PATHresolve_config / resolve_workflowHuman-readable wf/step|wf/step|... rendering of WORKFLOW_POSITION.
STEP_INPUTSresolve_configSingle-line JSON of a resolved step’s typed-input wiring (issue #813): the consumer step’s own bindings plus the call-frame chain (each frame’s callee, call step and frozen bindings) derived from the carried-forward WORKFLOW_POSITION. Emitted only when the run actually wires something. Empty/absent otherwise.
HISTORY_COMMENT_COUNTreusable workflow (history_comment_count)Thread-context bound (ADR-0003, issues #844/#872). Unset or non-positive → the prompt’s ## Recent thread renders the full relevant flow classified from the thread ledger; a positive N caps it to the last N relevant records (pinned comments always added on top). The shipped reusable workflow defaults to 5, so a CI run tail-caps the ledger unless the input is set to 0. The loop guard’s raw-history fallback still uses a positive default (5).
DEBUGuser (DEBUG=true in body) / flagEnables verbose debug logging.
OPENROUTER_API_KEYrepo secretLLM provider token (OpenRouter).
LOCAL_VLLM_API_TOKENrepo secret (optional)Local vLLM provider token.
COMMANDCODE_API_KEYrepo secret (optional)CommandCode subscription token — see subscription-plans.md.
GH_PATrepo secret (PAT fallback only)Fine-grained GitHub PAT used for git push and API calls when the Nebuko App is not installed. App-mode runs mint a short-lived installation token from the OIDC broker instead.

event_normalizer.py emits KEY=value pairs (multi-line values use HEREDOC form KEY<<EOF ... EOF) for eval $(...) consumption; query_context.py writes to GITHUB_ENV the same way.


MarkerPurpose
<!-- bot-workflow-terminal -->Universal bot marker prepended (line 1) to every CI-posted comment. Makes human-vs-bot classification deterministic. Defined as BOT_MARKER in github_client.py.
<!-- bot-agent-{name} -->Per-agent marker identifying which agent posted a comment (e.g., <!-- bot-agent-architect -->). Used by circuit breaker and metrics extraction.
<!-- bot-agent-script -->Pseudo-role marker for deterministic script steps (issue #541): the step output is posted under this fixed tag so repeated runs update rather than duplicate, and circuit-breaker marker counting stays intact.
<!-- AGENT_RUN_CONFIG ... -->Dispatch sentinel — see AGENT_RUN_CONFIG. Canonical regex in validate_output.py: <!--(?:[ \t])*AGENT_RUN_CONFIG[ \t]+(\{.*?\})(?:[ \t])*--> (single-line, no DOTALL).
<!-- WORKFLOW_STATE_START --> … <!-- WORKFLOW_STATE_END -->Collapsed JSON workflow-state section — see WORKFLOW_STATE v1. Per-step agent metrics live in its history entries.
<!-- po-agent-state: round=N,status=S -->Issue-refinement agent state markers (see AGENTS.md).