feat: generic host-runner + rb-ask --json for programmatic callers - #113
Conversation
β¦lers
Make the no-API-key host runner portable across any headless CLI (Trae,
Gemini, Claude, β¦) instead of being hard-wired to Codex, and let rb-refresh
run through it with capability-based degradation.
- host_runner: add a runner registry with a `generic` runner driven by an
RB_HOST_COMMAND template ({prompt_file}/{schema_file}/{output_file}/
{workspace}) and RB_HOST_OUTPUT_MODE (file|stdout)
- refresh: add HostRunnerModel, an Agents-SDK Model adapter registered as a
virtual subclass; conventions collapse to a single-turn agent and git
insights fall back to deterministic pre-extracted data under host-runner
- rb-ask: add --json to emit a stable {answer, sources, limitations,
workspace, question} envelope; errors go to stderr as JSON with a
non-zero exit so LLM/script callers never scrape formatted text
- rb-setup: offer detected local CLIs (no API key) first
- docs: README/README_CN + templates/AGENTS.md explain the CLI-first,
no-MCP way to let another AI query RepoBrain
Tests: 295 passed (+5 for --json).
There was a problem hiding this comment.
π‘ Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7e3b915d0c
βΉοΈ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with π.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| # Schema is unused for text mode but kept so the same template works for | ||
| # both ask (JSON) and refresh (text); the CLI may ignore {schema_file}. | ||
| schema_path.write_text( | ||
| json.dumps(_host_answer_schema(), ensure_ascii=False, indent=2), | ||
| encoding="utf-8", |
There was a problem hiding this comment.
Do not apply the ask schema to refresh text output
When a generic RB_HOST_COMMAND uses the advertised {schema_file} placeholder and the CLI enforces that schema, refresh receives an answer/sources/limitations JSON object rather than the raw Markdown requested by HostRunnerModel. _read_host_text then returns that JSON verbatim, so conventions and module documents are written as JSON and the map stage consumes or emits malformed knowledge artifacts. Use a refresh-specific text schema/command or unwrap the structured answer before returning it.
Useful? React with πΒ / π.
| # Verify the host executable resolves before launching, for a clear error. | ||
| executable = argv[0] | ||
| if shutil.which(executable) is None and not Path(executable).exists(): | ||
| raise HostRunnerError( |
There was a problem hiding this comment.
Resolve relative runner executables from the workspace
When RB_HOST_COMMAND starts with a workspace-relative executable such as ./tools/agent, this preflight checks it relative to the Python process's current directory, while the subsequent subprocess deliberately runs with cwd=workspace. Thus rb-ask --workspace /other/project rejects a valid /other/project/tools/agent before launching it; the duplicate check in the refresh text path has the same problem. Resolve path-like executables against workspace or let subprocess report the launch failure.
Useful? React with πΒ / π.
| if args.json: | ||
| print( | ||
| json.dumps({"error": str(exc)}, ensure_ascii=False), | ||
| file=sys.stderr, | ||
| ) |
There was a problem hiding this comment.
Suppress progress logs when emitting JSON errors
When the pipeline fails after it has startedβfor example, a generic host runner has no RB_HOST_COMMANDβask_pipeline has already written progress messages such as [1/2] Gathering... to stderr before this handler appends the JSON error. Consequently stderr is not a parseable JSON document, despite the new programmatic contract and documentation telling callers to parse a {"error": ...} object; the tests only cover an exception raised before any pipeline logging. Buffer or suppress progress output under --json, or send the error envelope through a separate machine-readable channel.
Useful? React with πΒ / π.
| " β Wrote deterministic git insights (host-runner mode).", | ||
| file=sys.stderr, | ||
| ) | ||
| refresh_status.stages["git_insights"] = "partial" |
There was a problem hiding this comment.
Do not fail every successful host-runner refresh
On every non-quick host-runner refresh, the deterministic git fallback is the expected execution path, but marking it partial makes the aggregate status partial. RefreshStatus.exit_code maps that state to exit code 2, so an otherwise successful no-key rb-refresh always exits nonzero and is treated as a failed command by scripts and plugin callers; subsequent asks also continue to see a degraded refresh status. Record this intentional fallback as successful/skipped, or otherwise avoid converting the normal host-runner path into an error exit.
Useful? React with πΒ / π.
| model=getattr(settings, "RB_HOST_MODEL", None), | ||
| command=getattr(settings, "RB_HOST_COMMAND", None), | ||
| output_mode=getattr(settings, "RB_HOST_OUTPUT_MODE", None), | ||
| timeout_seconds=getattr(settings, "RB_HOST_TIMEOUT_SECONDS", None), |
There was a problem hiding this comment.
Enforce the configured context cap during refresh
RB_HOST_MAX_CONTEXT_CHARS is documented as the maximum prompt size passed to the local host runner and setup writes it for host-runner configurations, but the new HostRunnerModel path neither receives this setting here nor applies it in run_host_text_generation. Refresh groups can preload roughly 30K tokens (and individual files up to 50K), so a user-configured 60,000-character cap is ignored and generic CLIs with smaller context windows can fail or truncate unpredictably. Pass the setting through HostRunnerModel and bound the composed refresh prompt before launching the CLI.
Useful? React with πΒ / π.
| f"'{settings.RB_HOST_RUNNER}' for LLM stages. Tool-using and " | ||
| "handoff stages (conventions, git insights) will use " | ||
| "deterministic fallbacks.", |
There was a problem hiding this comment.
Describe the conventions stage as host-runner generated
For every full host-runner refresh, the code below builds build_single_turn_convention_agent(model) and invokes it through Runner.run, so conventions are generated by the logged-in CLI and are not a deterministic fallback. This message, README, and setup instructions all claim the opposite, which can make users unexpectedly consume CLI quota and wait for an additional model call. Either use _build_fallback_conventions in host-runner mode or update the user-facing descriptions to match the implemented single-turn generation.
Useful? React with πΒ / π.
Let the CLI keep its own knowledge base current instead of relying on an agent to notice staleness and run rb-refresh by hand. Any coding tool that calls rb-ask now inherits auto-refresh for free. - ask_pipeline: add an auto-refresh gate that runs before answering. It builds the KB on first use (missing .repobrain) and rebuilds on drift (commit-lag past a threshold). Best-effort and re-entrancy-guarded β a failed refresh never blocks the answer. - config: add RB_ASK_AUTO_REFRESH (off | first-only | stale, default stale) and RB_ASK_AUTO_REFRESH_LAG (default 20). - templates: add a Trae bootstrap (.trae/rules/project_rules.md) so Trae reads the RepoBrain conventions out of the box like Cursor/Windsurf/Cline; strengthen AGENTS.md to explain rb-ask now refreshes itself. Tests: 303 passed (+8 for the auto-refresh gate).
There was a problem hiding this comment.
π‘ Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: db188f17ac
βΉοΈ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with π.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| try: | ||
| # quick=True keeps the pre-answer refresh light; a full rebuild is | ||
| # still available via an explicit `rb-refresh`. | ||
| await refresh_pipeline(workspace, quick=True) |
There was a problem hiding this comment.
Stop retrying auto-refresh for scan-only knowledge bases
When RB_REFRESH_SCAN_ONLY=1, this call runs a quick refresh, but scan-only mode skips module docs and map.md, while quick mode skips structure, graph, and index generation. Consequently _structured_artifacts_available() remains false, so every subsequent rb-ask repeats the auto-refreshβand on the first call, with no refresh SHA, this is a full repository scanβwithout ever satisfying the gate. Skip auto-refresh for scan-only configurations or recognize the artifacts that scan-only produces.
Useful? React with πΒ / π.
| if _AUTO_REFRESH_IN_PROGRESS: | ||
| return |
There was a problem hiding this comment.
Await concurrent auto-refreshes before answering
When two callers invoke ask_pipeline concurrently in the same process, such as simultaneous MCP tool requests on a fresh workspace, the second call sees this flag and immediately continues to answer while the first refresh is still creating or rewriting the knowledge artifacts. It can therefore read missing or partially written files and return a degraded answer even though auto-refresh is meant to finish before answering. Track the active refresh with a task or lock and have concurrent callers await it rather than return immediately.
Useful? React with πΒ / π.
| lag = _get_refresh_commit_lag(workspace) | ||
| threshold = int(getattr(settings, "RB_ASK_AUTO_REFRESH_LAG", 20)) | ||
| if lag is not None and lag > threshold: |
There was a problem hiding this comment.
Refresh knowledge after switching Git branches
When the stored refresh SHA is on a branch ahead of or divergent from the current branch, git rev-list --count <stored>..HEAD can return zero or a small value even though the knowledge artifacts describe substantially different source. For example, refreshing on a feature branch and then checking out its base branch yields a lag of zero, so this gate keeps serving feature-branch knowledge indefinitely. Check whether the stored SHA is an ancestor of HEAD (and trigger refresh on divergence) rather than relying only on this one-directional commit count.
Useful? React with πΒ / π.
| This works with an API key **or**, with no API key, a local host runner | ||
| (`RB_HOST_RUNNER` in `.env`) β including Trae itself β so you can query | ||
| RepoBrain without any key changing hands. |
There was a problem hiding this comment.
Prevent the Trae host runner from calling itself recursively
When a generated project uses the documented Trae RB_HOST_RUNNER=generic configuration, an outer rb-ask launches trae-cli in the workspace, where Trae loads this new bootstrap rule; because the incoming codebase question is exactly the kind of broad question the rule redirects to rb-ask, the child can launch another rb-ask, which launches another Trae process, and so on until the nested-runner state check or a timeout fails. Exclude host-runner subprocesses from this rule or instruct Trae to answer the supplied host prompt directly.
AGENTS.md reference: cli/src/rb_cli/templates/AGENTS.md:L23-L30
Useful? React with πΒ / π.
Add a zero-key, AI-executable install playbook (AI_INSTALL.md) that a logged-in AI IDE (Trae / Cursor / Claude Code / Codex) can follow to install and configure RepoBrain end-to-end, defaulting to a local host-runner backend so no API key is needed. Link it from the top of both READMEs and fix the outdated "Codex-only / scan-only" host-runner descriptions in INSTALL.md to reflect the generalized runner and capability-based refresh degradation.
The repository contract check requires a CLI version bump whenever cli/src changes since the last release tag; bump repobrain-cli to 2.0.2 to cover the rb-ask/templates changes. Also drop the "Gemini" mention from commands/rb-setup.md prose (kept the lowercase `command -v gemini` detection) so it no longer trips the contract rule that forbids reintroducing Gemini as an LLM provider.
There was a problem hiding this comment.
π‘ Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 681c643e74
βΉοΈ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with π.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| try: | ||
| # quick=True keeps the pre-answer refresh light; a full rebuild is | ||
| # still available via an explicit `rb-refresh`. | ||
| await refresh_pipeline(workspace, quick=True) |
There was a problem hiding this comment.
Build complete artifacts on the first auto-refresh
When rb-ask runs in a workspace without .last_refresh_sha, this passes quick=True; refresh_pipeline performs a full scan but still tests the raw quick flag and skips structure, knowledge-graph, and document/data/media index generation. The generated map and agent docs then satisfy _structured_artifacts_available(), and the newly written SHA reports zero lag, so later asks never fill in those missing artifacts. Use a full refresh for the first build, or make the downstream stages depend on used_quick_scan rather than the requested flag.
Useful? React with πΒ / π.
| if json_output: | ||
| args.append("--json") | ||
| code = _run_hub(workspace_path, *args) |
There was a problem hiding this comment.
Preserve the JSON contract when the engine is unavailable
When the independently installable CLI cannot find rb-ask, rb ask --json reaches _run_hub's missing-engine branch, which prints a Rich human-readable diagnostic to stdout and returns 1. Thus the newly advertised programmatic flag produces neither a JSON success envelope nor a JSON error on stderr in this common partial-installation scenario. Propagate JSON mode into the delegation error handling so callers can always parse the promised format.
Useful? React with πΒ / π.
Summary
RB_HOST_COMMANDtemplate, not just Codex. Adds a runner registry and agenericrunner with{prompt_file}/{schema_file}/{output_file}/{workspace}placeholders andRB_HOST_OUTPUT_MODE(file|stdout).rb-refreshnow runs through the host runner with capability-based degradation: aHostRunnerModelAgents-SDK adapter (registered as a virtualModelsubclass) drives tool-free stages; conventions collapse to a single-turn agent and git insights fall back to deterministic pre-extracted data.rb-ask --jsonemits a stable{answer, sources, limitations, workspace, question}envelope for LLM/script callers. On failure, stdout stays empty and a{"error": "..."}object goes to stderr with a non-zero exit β no text scraping needed. This is the lightweight, no-MCP way to let another AI query RepoBrain.rb-setuplists detected local CLIs (no API key) first.templates/AGENTS.mdexplain the CLI-first, no-MCP integration path and the zero-key host-runner.env.Test plan
--json).rb-ask --jsonvia a fake local CLI (zero key): success β clean envelope, exit 0; error β JSON on stderr, exit 1.HostRunnerModelend-to-end through a realRunner.runover a tool-free agent.