Skip to content

feat: generic host-runner + rb-ask --json for programmatic callers - #113

Merged
study8677 merged 4 commits into
mainfrom
feat/generic-host-runner
Aug 24, 2026
Merged

feat: generic host-runner + rb-ask --json for programmatic callers#113
study8677 merged 4 commits into
mainfrom
feat/generic-host-runner

Conversation

@study8677

Copy link
Copy Markdown
Owner

Summary

  • Generalize the no-API-key host runner so it drives any headless CLI (Trae, Gemini, Claude, …) via an RB_HOST_COMMAND template, not just Codex. Adds a runner registry and a generic runner with {prompt_file}/{schema_file}/{output_file}/{workspace} placeholders and RB_HOST_OUTPUT_MODE (file|stdout).
  • rb-refresh now runs through the host runner with capability-based degradation: a HostRunnerModel Agents-SDK adapter (registered as a virtual Model subclass) drives tool-free stages; conventions collapse to a single-turn agent and git insights fall back to deterministic pre-extracted data.
  • rb-ask --json emits 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-setup lists detected local CLIs (no API key) first.
  • Docs: README / README_CN + templates/AGENTS.md explain the CLI-first, no-MCP integration path and the zero-key host-runner .env.

Test plan

  • Full suite: 295 passed (+5 new for --json).
  • E2E rb-ask --json via a fake local CLI (zero key): success β†’ clean envelope, exit 0; error β†’ JSON on stderr, exit 1.
  • HostRunnerModel end-to-end through a real Runner.run over a tool-free agent.
  • Live run against a real logged-in CLI (Trae/Codex) β€” run in a normal terminal, not inside a TraeCode agent (avoids TRAE-in-TRAE state conflict):
    rb-refresh --workspace .
    rb-ask "How does this project work?" --workspace . --json

…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).

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ’‘ 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".

Comment on lines +731 to +735
# 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",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 πŸ‘Β / πŸ‘Ž.

Comment on lines +393 to 396
# 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(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 πŸ‘Β / πŸ‘Ž.

Comment on lines +218 to +222
if args.json:
print(
json.dumps({"error": str(exc)}, ensure_ascii=False),
file=sys.stderr,
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge 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"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge 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 πŸ‘Β / πŸ‘Ž.

Comment on lines +79 to +82
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),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 πŸ‘Β / πŸ‘Ž.

Comment on lines +279 to +281
f"'{settings.RB_HOST_RUNNER}' for LLM stages. Tool-using and "
"handoff stages (conventions, git insights) will use "
"deterministic fallbacks.",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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).

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ’‘ 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)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 πŸ‘Β / πŸ‘Ž.

Comment on lines +283 to +284
if _AUTO_REFRESH_IN_PROGRESS:
return

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 πŸ‘Β / πŸ‘Ž.

Comment on lines +264 to +266
lag = _get_refresh_commit_lag(workspace)
threshold = int(getattr(settings, "RB_ASK_AUTO_REFRESH_LAG", 20))
if lag is not None and lag > threshold:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 πŸ‘Β / πŸ‘Ž.

Comment on lines +27 to +29
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.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge 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.
@study8677
study8677 merged commit f8f600d into main Aug 24, 2026
9 checks passed
@study8677
study8677 deleted the feat/generic-host-runner branch August 24, 2026 07:08

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ’‘ 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)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 πŸ‘Β / πŸ‘Ž.

Comment thread cli/src/rb_cli/cli.py
Comment on lines +463 to +465
if json_output:
args.append("--json")
code = _run_hub(workspace_path, *args)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 πŸ‘Β / πŸ‘Ž.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant