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
186 changes: 186 additions & 0 deletions apps/loopover-ui/content/docs/ams-config-precedence.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
---
title: Miner config precedence
description: How AMS layers configuration across per-repo goal spec, operator env, CLI flags, and operator policy files -- the order each concern actually implements today.
---

AMS does **not** have a single `config.js` resolver. Configuration is layered by concern across
several modules under `packages/loopover-miner/lib/` and `@loopover/engine`. This document states
the order each layer **actually implements today** — not an idealized or corrected order.

## Configuration layers

<FeatureRow
items={[
{
title: "Per-target-repo file",
description:
".loopover-miner.yml (or .github/loopover-miner.yml, JSON variants), scoped to one cloned target repo. Modules: lib/miner-goal-spec.js, engine parseMinerGoalSpecContent.",
},
{
title: "Operator env",
description:
"LOOPOVER_MINER_* / MINER_*, scoped to this miner process / fleet container. Modules: lib/local-store.js, lib/governor-kill-switch.js, lib/attempt-cli.js, and others.",
},
{
title: "CLI flags",
description:
"loopover-miner <cmd> ... argv, scoped to one invocation. Modules: lib/attempt-cli.js, lib/discover-cli.js, lib/loop-cli.js, and others.",
},
{
title: "Operator file (not goal spec)",
description:
"~/.config/loopover-miner/.loopover-ams.yml, operator execution policy. Module: lib/ams-policy.js.",
},
]}
/>

`.loopover-miner.yml` is **maintainer-authored in the target repo**. Operator env and CLI flags
are **never overridden by a target repo's goal spec** for operator-owned policy (see
`lib/ams-policy.js`'s header).

## .loopover-miner.yml file discovery

First existing file wins (engine `MINER_GOAL_SPEC_FILENAMES`):

**1.** `.loopover-miner.yml`

**2.** `.github/loopover-miner.yml`

**3.** `.loopover-miner.json`

**4.** `.github/loopover-miner.json`

## Precedence by concern

### Kill switch (halt miner writes)

**Sources:** `LOOPOVER_MINER_KILL_SWITCH` (operator env) and `.loopover-miner.yml` →
`killSwitch.paused`.

**Order (safest wins, engine `resolveMinerKillSwitch`):**

**1.** Global env halt → scope `"global"` (always reported even when the repo yml also pauses).

**2.** Else per-repo yml `killSwitch.paused: true` → scope `"repo"`.

**3.** Else → scope `"none"`.

<Callout variant="note">
There is **no CLI flag** for kill-switch today. `MINER_CODING_AGENT_PAUSED` is a separate axis
(coding-agent spawn only) and does not change kill-switch scope.
</Callout>

### Governor live write mode

**Sources:** `LOOPOVER_MINER_LIVE_MODE=live` (operator env) and `.loopover-miner.yml` →
`execution.liveModeOptIn: live`.

**Order (engine `resolveMinerActionMode`):**

**1.** Kill switch active → `"paused"` (overrides any live opt-in).

**2.** Else **both** operator env **and** repo yml must equal the exact string `"live"` →
`"live"`.

**3.** Else → `"dry_run"`.

<Callout variant="warn">
This is an **AND** requirement, not "last writer wins". Either side missing or malformed →
dry-run. There is also **no CLI flag wired to governor live mode** today —
`attempt --live` / `loop --live` affect coding-agent spawn mode only (below).
</Callout>

### Coding-agent execution mode (spawn the driver?)

**Sources:** `MINER_CODING_AGENT_PAUSED` (operator env) and `attempt|loop --live` (CLI, per
invocation).

**Order (engine `resolveCodingAgentExecutionMode`, wired in `lib/attempt-cli.js`):**

**1.** Global env pause (`MINER_CODING_AGENT_PAUSED` truthy) → `"paused"`.

**2.** Else CLI `--live` absent → `agentDryRun: true` → `"dry_run"` (`attempt-cli.js` enforces
dry-run default).

**3.** Else CLI `--live` present → `"live"`.

<Callout variant="note">
There is no `.loopover-miner.yml` field for coding-agent mode today.
</Callout>

### Discover forge credential env var name

**Sources:** `discover --token-env <VAR>` (CLI), programmatic `options.tokenEnv`, forge default
(`GITHUB_TOKEN`).

**Order (`lib/discover-cli.js`):**

**1.** CLI `--token-env`

**2.** Else programmatic `options.tokenEnv`

**3.** Else `resolveForgeConfig(...).tokenEnvVar` (default `GITHUB_TOKEN`)

<Callout variant="note">
There is no `.loopover-miner.yml` forge block today; `--api-base-url` follows the same CLI →
programmatic → default shape for the API host.
</Callout>

### GitHub token value

**Sources:** `GITHUB_TOKEN` (operator env), a `loopover-mcp login` session recorded in the
`loopover-mcp` config file (`~/.config/loopover/config.json` by default), programmatic
`options.githubToken`.

**Order (`lib/github-token-resolution.js`'s `resolveGitHubToken`, called once at the top of each
CLI entrypoint — `loop`, `attempt`, `init --verify-token`, `manage poll` — then threaded down
explicitly to every real GitHub caller):**

**1.** Caller-supplied `options.githubToken` (an explicit override passed programmatically) wins
outright.

**2.** Else `GITHUB_TOKEN` env — an existing self-host operator's PAT setup keeps working
unchanged, no filesystem or network access.

**3.** Else a live token fetched from the authenticated `loopover-mcp login` session (`POST
/v1/auth/github/token`, cached in memory for the process's lifetime; a failed fetch is not
cached, so a later call retries rather than staying stuck).

**4.** Else `null` — the caller's own existing "no token" failure mode applies (git operations
requiring auth fail the same way they did before this feature existed).

<Callout variant="note">
This is a distinct concern from "Discover forge credential env var name" above, which resolves
the *name* of an env var to read, not the token *value* itself; `discover --token-env` is
unaffected by this section.
</Callout>

### Local SQLite store paths

**Sources:** per-store `LOOPOVER_MINER_*_DB` env var, then `LOOPOVER_MINER_CONFIG_DIR`, then XDG
default (`lib/local-store.js`).

Explicit per-store env **wins** over config dir; config dir **wins** over XDG. No CLI or
goal-spec override.

## Known gaps / inconsistencies

- **No unified precedence** across yml + env + CLI for a single knob — each concern owns its own
resolver.
- **Live execution** spans two independent gates: coding-agent `--live` (spawn) vs governor
env+yml (writes). Both must allow live for a full live open-pr attempt.
- **Forge tenant overrides** (`--api-base-url`, `--token-env`) are CLI/programmatic only;
`.loopover-miner.yml` cannot set them yet.
- **Operator AMS policy** (`.loopover-ams.yml`) is separate from per-repo goal spec; goal spec
never overrides operator policy.

<Callout variant="note">
If a future change adds yml or CLI for a setting documented here as env-only, this page and
`test/unit/miner-config-precedence.test.ts` both need updating.
</Callout>

## See also

- [`packages/loopover-miner/docs/miner-goal-spec.md`](https://github.com/JSONbored/loopover/blob/main/packages/loopover-miner/docs/miner-goal-spec.md) — goal-spec field reference
- [`packages/loopover-miner/docs/env-reference.md`](https://github.com/JSONbored/loopover/blob/main/packages/loopover-miner/docs/env-reference.md) — generated operator env list
- ORB's `.loopover.yml` precedence (`yml > DB > defaults`) in [Self-hosting configuration](/docs/self-hosting-configuration) — analogous documentation style, different runtime
1 change: 1 addition & 0 deletions apps/loopover-ui/src/components/site/docs-nav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ export const docsNav: DocsGroup[] = [
{ to: "/docs/ams-observability", label: "Observing your miner" },
{ to: "/docs/ams-unattended-scheduling", label: "Unattended scheduling" },
{ to: "/docs/ams-sizing", label: "Resource sizing" },
{ to: "/docs/ams-config-precedence", label: "Config precedence" },
],
},
],
Expand Down
21 changes: 21 additions & 0 deletions apps/loopover-ui/src/routeTree.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ import { Route as DocsAmsSizingRouteImport } from './routes/docs.ams-sizing'
import { Route as DocsAmsOperationsRunbookRouteImport } from './routes/docs.ams-operations-runbook'
import { Route as DocsAmsObservabilityRouteImport } from './routes/docs.ams-observability'
import { Route as DocsAmsDeploymentRouteImport } from './routes/docs.ams-deployment'
import { Route as DocsAmsConfigPrecedenceRouteImport } from './routes/docs.ams-config-precedence'
import { Route as DocsAiSummariesRouteImport } from './routes/docs.ai-summaries'
import { Route as AppWorkbenchRouteImport } from './routes/app.workbench'
import { Route as AppRunsRouteImport } from './routes/app.runs'
Expand Down Expand Up @@ -360,6 +361,11 @@ const DocsAmsDeploymentRoute = DocsAmsDeploymentRouteImport.update({
path: '/ams-deployment',
getParentRoute: () => DocsRoute,
} as any)
const DocsAmsConfigPrecedenceRoute = DocsAmsConfigPrecedenceRouteImport.update({
id: '/ams-config-precedence',
path: '/ams-config-precedence',
getParentRoute: () => DocsRoute,
} as any)
const DocsAiSummariesRoute = DocsAiSummariesRouteImport.update({
id: '/ai-summaries',
path: '/ai-summaries',
Expand Down Expand Up @@ -467,6 +473,7 @@ export interface FileRoutesByFullPath {
'/app/runs': typeof AppRunsRoute
'/app/workbench': typeof AppWorkbenchRoute
'/docs/ai-summaries': typeof DocsAiSummariesRoute
'/docs/ams-config-precedence': typeof DocsAmsConfigPrecedenceRoute
'/docs/ams-deployment': typeof DocsAmsDeploymentRoute
'/docs/ams-observability': typeof DocsAmsObservabilityRoute
'/docs/ams-operations-runbook': typeof DocsAmsOperationsRunbookRoute
Expand Down Expand Up @@ -535,6 +542,7 @@ export interface FileRoutesByTo {
'/app/runs': typeof AppRunsRoute
'/app/workbench': typeof AppWorkbenchRoute
'/docs/ai-summaries': typeof DocsAiSummariesRoute
'/docs/ams-config-precedence': typeof DocsAmsConfigPrecedenceRoute
'/docs/ams-deployment': typeof DocsAmsDeploymentRoute
'/docs/ams-observability': typeof DocsAmsObservabilityRoute
'/docs/ams-operations-runbook': typeof DocsAmsOperationsRunbookRoute
Expand Down Expand Up @@ -607,6 +615,7 @@ export interface FileRoutesById {
'/app/runs': typeof AppRunsRoute
'/app/workbench': typeof AppWorkbenchRoute
'/docs/ai-summaries': typeof DocsAiSummariesRoute
'/docs/ams-config-precedence': typeof DocsAmsConfigPrecedenceRoute
'/docs/ams-deployment': typeof DocsAmsDeploymentRoute
'/docs/ams-observability': typeof DocsAmsObservabilityRoute
'/docs/ams-operations-runbook': typeof DocsAmsOperationsRunbookRoute
Expand Down Expand Up @@ -680,6 +689,7 @@ export interface FileRouteTypes {
| '/app/runs'
| '/app/workbench'
| '/docs/ai-summaries'
| '/docs/ams-config-precedence'
| '/docs/ams-deployment'
| '/docs/ams-observability'
| '/docs/ams-operations-runbook'
Expand Down Expand Up @@ -748,6 +758,7 @@ export interface FileRouteTypes {
| '/app/runs'
| '/app/workbench'
| '/docs/ai-summaries'
| '/docs/ams-config-precedence'
| '/docs/ams-deployment'
| '/docs/ams-observability'
| '/docs/ams-operations-runbook'
Expand Down Expand Up @@ -819,6 +830,7 @@ export interface FileRouteTypes {
| '/app/runs'
| '/app/workbench'
| '/docs/ai-summaries'
| '/docs/ams-config-precedence'
| '/docs/ams-deployment'
| '/docs/ams-observability'
| '/docs/ams-operations-runbook'
Expand Down Expand Up @@ -1252,6 +1264,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof DocsAmsDeploymentRouteImport
parentRoute: typeof DocsRoute
}
'/docs/ams-config-precedence': {
id: '/docs/ams-config-precedence'
path: '/ams-config-precedence'
fullPath: '/docs/ams-config-precedence'
preLoaderRoute: typeof DocsAmsConfigPrecedenceRouteImport
parentRoute: typeof DocsRoute
}
'/docs/ai-summaries': {
id: '/docs/ai-summaries'
path: '/ai-summaries'
Expand Down Expand Up @@ -1417,6 +1436,7 @@ const AppRouteWithChildren = AppRoute._addFileChildren(AppRouteChildren)

interface DocsRouteChildren {
DocsAiSummariesRoute: typeof DocsAiSummariesRoute
DocsAmsConfigPrecedenceRoute: typeof DocsAmsConfigPrecedenceRoute
DocsAmsDeploymentRoute: typeof DocsAmsDeploymentRoute
DocsAmsObservabilityRoute: typeof DocsAmsObservabilityRoute
DocsAmsOperationsRunbookRoute: typeof DocsAmsOperationsRunbookRoute
Expand Down Expand Up @@ -1462,6 +1482,7 @@ interface DocsRouteChildren {

const DocsRouteChildren: DocsRouteChildren = {
DocsAiSummariesRoute: DocsAiSummariesRoute,
DocsAmsConfigPrecedenceRoute: DocsAmsConfigPrecedenceRoute,
DocsAmsDeploymentRoute: DocsAmsDeploymentRoute,
DocsAmsObservabilityRoute: DocsAmsObservabilityRoute,
DocsAmsOperationsRunbookRoute: DocsAmsOperationsRunbookRoute,
Expand Down
49 changes: 49 additions & 0 deletions apps/loopover-ui/src/routes/docs.ams-config-precedence.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { createFileRoute, notFound } from "@tanstack/react-router";
import { Suspense } from "react";

import { DocsPage } from "@/components/site/docs-page";
import { docsClientLoader } from "@/lib/docs-client-loader";

// Rendered from content/docs/ams-config-precedence.mdx via fumadocs-mdx's browser entry
// (docsClientLoader), through the existing DocsPage/Callout/CodeBlock/FeatureRow
// primitives -- not fumadocs-ui's bundled components. See docs-source.ts's comment
// for why the loader below resolves only a plain, serializable path string.
export const Route = createFileRoute("/docs/ams-config-precedence")({
loader: async () => {
const { docsSource } = await import("@/lib/docs-source");
const page = docsSource.getPage(["ams-config-precedence"]);
if (!page) throw notFound();
return { path: page.path, title: page.data.title, description: page.data.description };
},
head: () => ({
meta: [
{ title: "Miner config precedence — LoopOver docs" },
{
name: "description",
content:
"How AMS layers configuration across per-repo goal spec, operator env, CLI flags, and operator policy files -- the order each concern actually implements today.",
},
{ property: "og:title", content: "Miner config precedence — LoopOver docs" },
{
property: "og:description",
content:
"How AMS layers configuration across per-repo goal spec, operator env, CLI flags, and operator policy files -- the order each concern actually implements today.",
},
{ property: "og:url", content: "/docs/ams-config-precedence" },
],
links: [{ rel: "canonical", href: "/docs/ams-config-precedence" }],
}),
component: AmsConfigPrecedence,
});

function AmsConfigPrecedence() {
const { path, title, description } = Route.useLoaderData();
const Content = docsClientLoader.getComponent(path);
return (
<DocsPage eyebrow="Maintainers" title={title} description={description}>
<Suspense fallback={<p className="text-token-sm text-muted-foreground">Loading…</p>}>
<Content />
</Suspense>
</DocsPage>
);
}
1 change: 1 addition & 0 deletions apps/loopover-ui/src/routes/docs.index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ const AUDIENCES: Audience[] = [
{ to: "/docs/ams-observability", label: "Observing your miner" },
{ to: "/docs/ams-unattended-scheduling", label: "Unattended scheduling" },
{ to: "/docs/ams-sizing", label: "Resource sizing" },
{ to: "/docs/ams-config-precedence", label: "Config precedence" },
{ to: "/docs/self-hosting-docs-audit", label: "Self-host docs audit" },
{ to: "/docs/maintainer-install-trust", label: "Install & trust guide" },
{ to: "/docs/github-app", label: "GitHub App configuration" },
Expand Down
4 changes: 4 additions & 0 deletions packages/loopover-miner/docs/config-precedence.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Miner config precedence

> Also published on the docs website: [Miner config precedence](https://loopover.ai/docs/ams-config-precedence)
> (same content, rendered with search and the rest of the maintainer docs nav). This file remains
> the canonical source and ships inside the published `@loopover/miner` package.

AMS does **not** have a single `config.js` resolver. Configuration is layered by concern across several modules under `packages/loopover-miner/lib/` and `@loopover/engine`. This document states the order each layer **actually implements today** — not an idealized or corrected order.

## Configuration layers
Expand Down
Loading