Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion docs/DASHBOARD.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,11 @@ Overview keeps status and routing in one health-first area:
- **Hosts & Routing** presents execution-host health, the primary-host policy, per-activity routes,
escalation paths, and routed host models. A configured route is assignment intent, not evidence
of which inference provider served a particular session.
- **Providers** presents inference-provider bindings and their configuration provenance.
- **Providers** presents inference-provider bindings and their configuration provenance. A
registered provider is eligible configuration, not evidence that a request selected or used it.
Direct Ruflo agents must explicitly select OpenRouter or Ollama together with a provider-native
model, and the Ruflo/MCP process must inherit the required credential environment. Served-provider
and served-model claims come from **Usage → Scorecard** evidence instead.
- **Runtime** presents operational services, processes, and MCP readiness.
- **Intelligence** presents memory, learning, and quality-improvement signals machine-wide: an
always-visible rollup folded across every project on this machine where memory or intelligence has
Expand Down Expand Up @@ -174,6 +178,13 @@ the serving provider, so a historical row may honestly show **Not recorded**. Th
evidence, not a claim that Claude Code was served by Anthropic or by any provider inferred from its
model.

This distinction also applies to Ruflo's project-scoped `agents.providers` registry. Ruflo 3.38.8+
can execute an explicitly spawned `--provider openrouter --model z-ai/glm-5.2` agent through
OpenRouter, but registration by `ak host pick --provider openrouter:z-ai/glm-5.2` does not retarget
every direct agent. `RUFLO_PROVIDER=openrouter` is a process-wide override; explicit per-agent
provider and model selection is the reproducible path. Restart a long-lived Ruflo/MCP process after
adding `OPENROUTER_API_KEY`, because it inherits environment variables only when it starts.

Usage transcript masking happens on the server. Redaction is marked, there is no reveal or export
control, and the original masked value never reaches the browser. See
[ADR-0009](adr/0009-usage-scorecard-local-transcript-analytics.md) for the full evidence and pricing
Expand Down
78 changes: 66 additions & 12 deletions docs/PROVIDERS.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,12 @@ however the endpoint is served. `local-openai` is not an AQE provider type — `
credentials, fragments, or secret-bearing query parameters. See
[ADR-0028](adr/0028-local-openai-compatible-providers.md).

A binding declares a compatible relationship; it does not add a new execution branch inside an
upstream tool. In particular, Ruflo's direct `agent_execute` path currently dispatches persisted
provider configuration through its explicit Ollama and OpenRouter branches. A `local-openai`
binding remains valid for Codex/OpenCode configuration without implying that Ruflo can select the
literal provider id `local-openai` for direct execution.

## External host adapters (experimental)

Want `ak` to manage a host CLI it doesn't ship in-tree — driving local models through something
Expand Down Expand Up @@ -161,24 +167,71 @@ for you:
ℹ codex CLI detected — run `ak host pick` to let ruflo use both claude and codex
```

## Two configs, one front door
## Native configs, one front door

A question that trips people up: **do ruflo and agentic-qe read the same config?** No.
They are two independent routing subsystems, each with its **own config store and its own
escalation machinery**. There is no shared file they both read. What unifies them is `ak`:
A question that trips people up: **do Ruflo and agentic-qe read the same config?** No.
They are independent routing subsystems, each with its **own config store**. There is no shared
file they both read. What unifies them is `ak`:
it takes your intent once (in `kit.json`) and writes **each tool's own native config** —
converging, proving, and able to undo. `ak` is a facilitator, not a config layer the tools
depend on.

| | **agentic-qe** (`HybridRouter`) | **ruflo** (provider router) |
| | **agentic-qe** (`HybridRouter`) | **Ruflo direct agent execution** |
|---|---|---|
| **Config store** | `.agentic-qe/llm-config.json` (per project) + env | `~/.agentic-flow/router.config.json` (or `--router-config`) + env |
| **Precedence** (highest wins) | explicit override → env (`AQE_LLM_*`, API keys) → disk file → built-in defaults | file (`defaultProvider`, `fallbackChain`) + CLI overrides |
| **Config store** | `.agentic-qe/llm-config.json` (per project) + env | `claude-flow.config.json` or `.claude-flow/config.json` (per project), `CLAUDE_FLOW_CONFIG`, and env |
| **Precedence** (highest wins) | explicit override → env (`AQE_LLM_*`, API keys) → disk file → built-in defaults | per-agent provider → `RUFLO_PROVIDER` → credential env → persisted `agents.providers` → inference/default |
| **Change the provider** | `AQE_LLM_PROVIDER=<type>` (env) — a provider whose API key is in the env is auto-enabled | `ruflo providers configure -p <id> -m <model>` |
| **Change the model** | per-provider `models` in the chain; per-activity `agentOverrides` (aqe ≥ 3.13.1) | `models.{default,fast,advanced}` per provider in the file |
| **Escalation / fallback** | ordered `fallbackChain` + circuit breaker + retry/backoff | `fallbackChain` + `routing.mode` (cost/quality/perf/rule-based) + circuit breaker |
| **Change the model** | per-provider `models` in the chain; per-activity `agentOverrides` (aqe ≥ 3.13.1) | agent model first; then the selected persisted provider's `model` |
| **Escalation / fallback** | ordered `fallbackChain` + circuit breaker + retry/backoff | provider selection for this path; not the enhanced router fallback chain |
| **The `ak` way** | `--aqe-provider` / `--aqe-fallback` / `--route` | `--provider <id>:<model>` |

Ruflo also bundles agentic-flow's enhanced model router, whose separate store is
`~/.agentic-flow/router.config.json` (or `--router-config`). `ak --provider` does not write that
router's `defaultProvider`, `fallbackChain`, or routing modes; it maps specifically to
`ruflo providers configure` and the project-scoped `agents.providers` registry.

Ruflo 3.38.8 fixed direct agent execution so it can consume that persisted registry
([upstream #2962](https://github.com/ruvnet/ruflo/issues/2962)). Registration and selection remain
different operations: `--provider` registers an eligible provider/model; an explicit per-agent
provider or `RUFLO_PROVIDER` still outranks it. OpenRouter registered by `ak` also needs
`OPENROUTER_API_KEY` in the environment. For a fresh keyless Ollama entry with no endpoint env,
`ak` supplies `http://127.0.0.1:11434`; it preserves an existing Ruflo `baseUrl`, and an explicit
`endpoint` on the `kit.json` model entry wins. `OLLAMA_API_KEY` keeps Ruflo's cloud behavior, while
`OLLAMA_BASE_URL` remains the environment override. `ak` surfaces a degraded warning when the
installed Ruflo is older than 3.38.8.

For a direct Ruflo agent using an OpenRouter-vended model, the complete user path is:

```bash
# Export this where the long-lived Ruflo/MCP process will inherit it, then restart that process.
export OPENROUTER_API_KEY=...

# Persist eligible provider/model intent. `ak sync` reapplies it later.
ak host pick --provider 'openrouter:z-ai/glm-5.2'

# Select the provider and model for the direct agent that will execute.
ruflo agent spawn --type coder --provider openrouter --model z-ai/glm-5.2
```

Execute the returned agent id through Ruflo's `agent_execute` MCP tool or a Ruflo workflow. Spawning
an agent, even with `--task`, is registration rather than execution. Treat the response's served
model/provider evidence as the proof that routing occurred. Omitting the provider/model flags does
not mean the `ak`-registered model becomes every agent's default. `RUFLO_PROVIDER=openrouter` can
force the provider for the whole Ruflo process, but explicit per-agent selection is narrower and
reproducible.

`ak run` is a separate host-execution path: it invokes Claude, Codex, or OpenCode adapters and does
not dispatch through Ruflo's direct-provider registry. To use an OpenRouter model in an `ak run`
pipeline, route an OpenCode activity to its provider-qualified model, for example:

```bash
ak run feature "implement the change" \
--route 'implementation:opencode:openrouter/z-ai/glm-5.2'
```

That path requires OpenCode's OpenRouter authentication/configuration; the Ruflo provider entry is
not a substitute for the host adapter's own credentials.

Two axes cut across both (see the intro): **hosts** (which agent CLI runs the ruflo loop —
`ENABLE_CLAUDE_CODE` / `ENABLE_CODEX`) are separate from **providers** (which LLM the routers
use). Level 4 below is the full `ak`-way ↔ raw-tool-way map for every knob in this table.
Expand Down Expand Up @@ -395,13 +448,14 @@ is the tool's own native config, and you can set it by hand — or let `ak` and
coexist. `ak` merges-not-clobbers and backs up first, mirroring how rUv itself layers config
(`mergeWithDefaults(config, defaults)` — sensible defaults, override with your partial).

The two config stores each knob below lives in — and their precedence — are summarized in
[Two configs, one front door](#two-configs-one-front-door) above.
The native config stores each knob below lives in — and their precedence — are summarized in
[Native configs, one front door](#native-configs-one-front-door) above.

| You want to… | `ak` way | The raw ruflo/aqe way it maps to |
| ------------------------------------ | --------------------------------- | --------------------------------------------------- |
| Enable claude/codex hosts | `ak host pick` | `ENABLE_CLAUDE_CODE` / `ENABLE_CODEX` env + managed bridge/guidance |
| Register a ruflo LLM provider | `--provider openai:gpt-5.6` | `ruflo providers configure -p openai -m gpt-5.6` |
| Register a Ruflo LLM provider | `--provider ollama:qwen3.6:27b` | `ruflo providers configure -p ollama -m qwen3.6:27b -e http://127.0.0.1:11434` |
| Select a direct Ruflo provider | per-agent/raw setting | agent `--provider` or `RUFLO_PROVIDER=ollama` / `openrouter` |
| Set which LLM runs QE | `--aqe-provider gemini` | `AQE_LLM_PROVIDER=gemini` (env) |
| Order QE's fallback chain | `--aqe-fallback '…'` | edit `.agentic-qe/llm-config.json` / `aqe llm-router config` |
| Cap QE spend | (kit.json `maxBudgetUsd`) | `AQE_MAX_BUDGET_USD` / `--max-budget-usd` |
Expand Down
14 changes: 12 additions & 2 deletions docs/adr/0028-local-openai-compatible-providers.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@

- **Status:** Accepted
- **Date:** 2026-08-11
- **Updated:** 2026-08-14
- **Updated:** 2026-08-25
- **Update note:** Accepted with corrections after review of PR #131: the quoted Hermes
`api_mode: openai` value is annotated as invalid rather than reproduced as valid (F-30), and the
AQE-projection asymmetry between `ollama` and `local-openai` is now stated explicitly as
intentional (F-29). Implemented with in-tree projections `['ruflo', 'codex', 'opencode']`.
Reconciled after ruvnet/ruflo#2962: projection is configuration eligibility, not proof that
Ruflo's direct `agent_execute` dispatcher implements an arbitrary provider id.
- **Deciders:** agentic-kit maintainers
- **Related:** [ADR-0011](0011-local-model-provenance-zero-cost-and-transcript-fidelity.md),
[ADR-0016](0016-capability-driven-integration-adapters.md),
Expand Down Expand Up @@ -128,12 +130,20 @@ deliberate:
because AQE does not. `ak status`'s provider surface reflects this distinction; surfacing it
clearly is a sibling work package's scope, not this ADR's.

`'ruflo'` has the same bounded meaning as every projection in this domain: the relationship can be
represented on Ruflo's provider-configuration surface. It does **not** claim that Ruflo's direct
agent executor dispatches an arbitrary provider name. Ruflo 3.38.8 fixed persisted provider
selection for its implemented Ollama and OpenRouter branches (ruvnet/ruflo#2962); it did not add a
generic `local-openai` branch. Therefore a `local-openai` binding is not direct-agent-execution
evidence, and agentic-kit must not present registration as successful selection or execution.

## Consequences

- A machine serving models from MLX, LM Studio, llama.cpp, vLLM, or anything else speaking
OpenAI-compatible HTTP on loopback can be described to ak without a new ADR per vendor, and
without inventing a provider id the user did not choose.
- Every host that can be pointed at an OpenAI-compatible base URL gains a nameable local provider.
- Every host with a concrete OpenAI-compatible projection can gain a nameable local provider; a
registry projection alone does not make another tool's direct executor support that provider id.
- The generic row deliberately supports **less** than `ollama`: no catalogue, no runtime probe, no
digest. Surfaces that show local-model detail for Ollama will show less for `local-openai`, and
that gap is the honest reading of the evidence, not a defect to paper over.
Expand Down
4 changes: 3 additions & 1 deletion docs/ddd/integration-management.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,9 @@ participate in other workflows.

An inference provider describes credentials, transports, projections, observability sources, and
bounded capabilities such as discovery, quota, pricing, and cache accounting. Provider identity
does not imply a host or routing capability.
does not imply a host or routing capability. A projection declares that configuration can be
represented on a native surface; it does not manufacture an execution branch in that tool.
Registration, selection, and observed execution remain separate facts.

### Provider binding

Expand Down
6 changes: 4 additions & 2 deletions src/commands/setup.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -415,11 +415,13 @@ export async function run_project({ flags, cfg, trustDisclosed = false }) {
const rmcp = await ensureRufloMcpInCodex(cfg, root);
if (rmcp.changed) saveKitConfig(cfg); // persist reverse MCP ownership
if (rmcp.changed || !rmcp.ok) (rmcp.ok ? ok : warn)(`ruflo→codex MCP: ${rmcp.detail}`);
const prov = await applyProviders(cfg, root);
if (prov.changed) (prov.ok ? ok : warn)(`providers: ${prov.detail}`);
} else if (await have('codex')) {
info('codex CLI detected — enable dual-host with: ak host pick');
}
// Provider routing is independent of the enabled execution-host set. Apply
// persisted Ruflo providers for Claude-only setups too (#128 / ruflo#2962).
const prov = await applyProviders(cfg, root);
if (prov.changed || !prov.ok || prov.status === 'degraded') reportOutcome('providers', prov);

// 10. statusline footer — LAST, after ruflo + aqe have settled the helper.
// A still-missing footer is a WARN (not silent info): it means the AQE /
Expand Down
37 changes: 35 additions & 2 deletions src/commands/status.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,13 @@ import { listDaemons, staleDaemons } from '../lib/daemons.mjs';
import { scanRvf } from '../lib/rvf.mjs';
import { registry, syncBlocks, blocksForTarget, retiredForTarget, guidanceTargets } from '../lib/blocks.mjs';
import { loadKitConfig } from '../lib/config.mjs';
import { driftReport, selfDrift } from '../lib/versions.mjs';
import { driftReport, selfDrift, installedVersion, cmpVersions } from '../lib/versions.mjs';
import { upstreamCveCounterFabricated, fixStatusline, helperStampStale } from '../lib/statusline.mjs';
import { drift as ruvnetBrainDrift, nightlyAgentPresent as rbNightlyPresent, NIGHTLY_LABEL as RB_NIGHTLY_LABEL } from '../lib/ruvnet-brain.mjs';
import { coherence as adbCoherence } from '../lib/agentdb.mjs';
import { readJson } from '../lib/settings.mjs';
import { have } from '../lib/exec.mjs';
import { HOSTS, settingsTarget, isDefault, managedEnv, MANAGED_ENV_KEYS, hostInstallState, hostAuthState, bothHostsEnabled, aqeRouterFile, aqeSupportsAgentOverrides, credentialGaps, collectIntegrationFacts } from '../lib/providers.mjs';
import { HOSTS, settingsTarget, isDefault, managedEnv, MANAGED_ENV_KEYS, hostInstallState, hostAuthState, bothHostsEnabled, aqeRouterFile, aqeSupportsAgentOverrides, credentialGaps, collectIntegrationFacts, MIN_RUFLO_PERSISTED_PROVIDER_VERSION } from '../lib/providers.mjs';
import { hostsWithLifecycle, isBuiltinHost, lifecycleExecutionEnabled } from '../lib/adapters/lifecycle-registry.mjs';
import { PROVIDER_REGISTRY } from '../lib/adapters/index.mjs';
import { configuredPolicyToAgentOverrides, agentOverridesDrift, routingSummary, divergedRoutes } from '../lib/routing.mjs';
Expand Down Expand Up @@ -686,6 +686,39 @@ export async function collect({ pkgRoot, cwd = process.cwd() }) {
}
}
}
// A kit.json provider/model entry is registration intent. Ruflo >=3.38.8
// can honor explicit OpenRouter/Ollama provider+model selection, but the
// registry does not retarget every agent and it is not execution evidence.
// Keep that distinction in the status rows the dashboard consumes.
const rufloModels = cfg.providers?.models ?? [];
if (rufloModels.length) {
const intent = rufloModels
.filter((entry) => entry?.id)
.map((entry) => `${entry.id}${entry.model ? `:${entry.model}` : ''}`)
.join(', ');
const rufloVersion = installedVersion('ruflo');
const affected = !!rufloVersion
&& cmpVersions(rufloVersion, MIN_RUFLO_PERSISTED_PROVIDER_VERSION) < 0;
const missingOpenRouterKey = rufloModels.some((entry) => entry?.id === 'openrouter')
&& !integrationFacts.providers?.openrouter?.credentialPresent;
const directIds = new Set(['ollama', 'openrouter']);
const registryOnly = [...new Set(rufloModels
.map((entry) => entry?.id)
.filter((id) => id && !directIds.has(id)))];
if (affected) {
rows.push(row('providers', 'warn',
`ruflo provider intent: ${intent} — ruflo ${rufloVersion} cannot honor persisted provider/model execution; needs >=${MIN_RUFLO_PERSISTED_PROVIDER_VERSION}`));
} else if (missingOpenRouterKey) {
rows.push(row('providers', 'warn',
`ruflo provider intent: ${intent} — direct agents must select provider + model; openrouter needs OPENROUTER_API_KEY in the Ruflo/MCP process`));
} else {
const unsupported = registryOnly.length
? `; no direct-agent execution branch for ${registryOnly.join(', ')}`
: '';
rows.push(row('providers', 'info',
`ruflo provider intent: ${intent} — direct agents must select provider + model; Usage proves served execution${unsupported}`));
}
}
// ADR-0028 F-29: local-openai is a local ($0) provider deliberately NOT
// projected to 'aqe' (unlike ollama, which is) — surface that asymmetry
// plainly so it reads as a fact, not a bug. Registry-driven (billing +
Expand Down
Loading
Loading