Skip to content
Closed
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
1 change: 1 addition & 0 deletions apps/loopover-ui/content/docs/ai-summaries.mdx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
---
title: Optional AI summaries
description: A short natural-language summary over the deterministic response. Off by default. Never the source of truth.
eyebrow: Roadmap · exploring
---

## The rule
Expand Down
11 changes: 6 additions & 5 deletions apps/loopover-ui/content/docs/ams-config-precedence.mdx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
---
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.
eyebrow: Maintainers
---

AMS does **not** have a single `config.js` resolver. Configuration is layered by concern across
Expand Down Expand Up @@ -87,8 +88,8 @@ First existing file wins (engine `MINER_GOAL_SPEC_FILENAMES`):

<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).
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?)
Expand Down Expand Up @@ -151,9 +152,9 @@ cached, so a later call retries rather than staying stuck).
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.
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
Expand Down
21 changes: 11 additions & 10 deletions apps/loopover-ui/content/docs/ams-deployment.mdx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
---
title: AMS deployment guide
description: Deploy @loopover/miner in laptop mode (single machine, zero Docker) or fleet mode (containerized workers) -- both 100% client-side, credentials never baked into images.
eyebrow: Maintainers
---

Two form factors for running `@loopover/miner`: **laptop mode** (single machine, zero Docker) and
Expand Down Expand Up @@ -57,9 +58,9 @@ loopover-miner init --interactive # optional: guided prompt for GITHUB_TOKEN
`init --interactive` offers "Authorize with GitHub" (device flow — visit a URL, enter a short
code, no token to copy or paste) as its first option once the miner's GitHub App OAuth client is
configured; the original pasted-PAT prompt stays available as option 2, and is what the wizard
falls back to automatically on any device-flow failure. Unconfigured, the wizard is
byte-identical to the pasted-token-only prompt. Either way, the resulting `GITHUB_TOKEN` acts as
your own GitHub account — there is no separate bot identity.
falls back to automatically on any device-flow failure. Unconfigured, the wizard is byte-identical
to the pasted-token-only prompt. Either way, the resulting `GITHUB_TOKEN` acts as your own GitHub
account — there is no separate bot identity.
</Callout>

### Expected layout after first use
Expand Down Expand Up @@ -150,9 +151,9 @@ The image entrypoint is `loopover-miner`; pass subcommands after the image name
config` and any full-env dump of the running container. For Docker Swarm/Kubernetes-managed
secrets (mounted as a file, e.g. at `/run/secrets/github_token`), set `GITHUB_TOKEN_FILE` to that
mount path instead — the miner reads and trims the file's contents at startup and uses it exactly
as if `GITHUB_TOKEN` had been set directly. If both are set, the plain `GITHUB_TOKEN` value
always wins. A missing or unreadable `GITHUB_TOKEN_FILE` fails the container fast with a clear
error naming the file path, rather than silently proceeding with no credential.
as if `GITHUB_TOKEN` had been set directly. If both are set, the plain `GITHUB_TOKEN` value always
wins. A missing or unreadable `GITHUB_TOKEN_FILE` fails the container fast with a clear error
naming the file path, rather than silently proceeding with no credential.
</Callout>

<CodeBlock
Expand Down Expand Up @@ -187,10 +188,10 @@ docker compose -f docker-compose.miner.yml up -d --build`}
<Callout variant="warn" title="Scaling to N parallel workers">
`docker compose -f docker-compose.miner.yml up -d --scale miner=N` gives every replica the
**same** `miner-data` volume — and the miner's SQLite ledgers are **not** safe for concurrent
access, so N replicas on one volume will contend/corrupt. To run N **isolated** workers, give
each its own state: run N separate compose projects (`docker compose -p miner-1 …`, `-p miner-2
…` — `-p` namespaces the volume) or point each at a distinct `LOOPOVER_MINER_CONFIG_DIR` on its
own mount. For built-in isolated horizontal scaling, use the Kubernetes StatefulSet in
access, so N replicas on one volume will contend/corrupt. To run N **isolated** workers, give each
its own state: run N separate compose projects (`docker compose -p miner-1 …`, `-p miner-2 …` —
`-p` namespaces the volume) or point each at a distinct `LOOPOVER_MINER_CONFIG_DIR` on its own
mount. For built-in isolated horizontal scaling, use the Kubernetes StatefulSet in
[`k8s/`](https://github.com/JSONbored/loopover/tree/main/k8s) (per-pod volumes).
</Callout>

Expand Down
1 change: 1 addition & 0 deletions apps/loopover-ui/content/docs/ams-discovery-plane.mdx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
---
title: Hosted discovery plane — operator guide (opt-in)
description: How a loopover-miner instance opts into the optional hosted discovery-index plane, what it may send, and what never leaves the operator's machine.
eyebrow: Maintainers
---

Operator-facing guide for the **optional** hosted discovery-index plane. This is the client/miner
Expand Down
1 change: 1 addition & 0 deletions apps/loopover-ui/content/docs/ams-env-reference.mdx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
---
title: AMS environment variable reference
description: Every LOOPOVER_MINER_* / MINER_* env var the miner reads, where it's first referenced in code, and its default -- generated from source, never hand-copied.
eyebrow: Maintainers
---

import { AMS_ENV_REFERENCE_MARKDOWN } from "@/lib/ams-env-reference";
Expand Down
5 changes: 3 additions & 2 deletions apps/loopover-ui/content/docs/ams-fleet-manifest.mdx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
---
title: Fleet run-manifest
description: The top-level config a fleet operator authors to run the miner across many repos at once -- which repos are in scope and how the worktree/concurrency budget is split.
eyebrow: Maintainers
---

The **fleet run-manifest** is the top-level config a *fleet operator* authors to run the miner
The **fleet run-manifest** is the top-level config a _fleet operator_ authors to run the miner
across many repos at once: it declares which repos are in scope for a fleet run and how a finite
worktree/concurrency budget is split between them. It is parsed by
`parseFleetRunManifestContent` / `parseFleetRunManifest` in `@loopover/engine`
Expand Down Expand Up @@ -50,7 +51,7 @@ opposite direction of intent:

This module produces only the parsed, typed manifest. Driving the fleet concurrency allocator
from it is the allocator's own concern, and the cross-repo `portfolio-queue.js` backlog reads the
same repo list — both *consume* this manifest; neither wiring lives here.
same repo list — both _consume_ this manifest; neither wiring lives here.

## Example (`fleet-run.yml`)

Expand Down
18 changes: 9 additions & 9 deletions apps/loopover-ui/content/docs/ams-goal-spec.mdx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
---
title: "MinerGoalSpec (.loopover-miner.yml)"
description: Per-repo configuration telling an autonomous LoopOver miner what to look for and how to behave when targeting a repo -- the full field reference.
eyebrow: Maintainers
---

Per-repo configuration telling an autonomous LoopOver miner what to look for and how to behave
Expand All @@ -17,8 +18,8 @@ Discovery order (first match wins):
- `.github/loopover-miner.json`

<Callout variant="note">
Every field is optional. Unknown keys are ignored; a malformed field falls back to its
documented default with a warning — a broken file never hard-fails the miner.
Every field is optional. Unknown keys are ignored; a malformed field falls back to its documented
default with a warning — a broken file never hard-fails the miner.
</Callout>

## Relationship to `.loopover.yml`
Expand Down Expand Up @@ -113,13 +114,12 @@ Per-repo kill-switch consulted by the Governor chokepoint before every write act
Per-repo dry-run/live execution opt-in consulted by the Governor chokepoint.

<Callout variant="warn">
A freshly-configured miner always defaults to dry-run (observe/log only, never execute a
write) — this field is the only per-repo path to live mode, and it alone is not sufficient: the
miner's own operator must also separately opt in globally (env var
`LOOPOVER_MINER_LIVE_MODE=live`) before writes actually execute. A repo that wants to guarantee
it never receives live automated writes, regardless of any operator's global setting, should use
`killSwitch.paused: true` instead — the kill-switch always takes precedence over any live-mode
opt-in.
A freshly-configured miner always defaults to dry-run (observe/log only, never execute a write) —
this field is the only per-repo path to live mode, and it alone is not sufficient: the miner's own
operator must also separately opt in globally (env var `LOOPOVER_MINER_LIVE_MODE=live`) before
writes actually execute. A repo that wants to guarantee it never receives live automated writes,
regardless of any operator's global setting, should use `killSwitch.paused: true` instead — the
kill-switch always takes precedence over any live-mode opt-in.
</Callout>

- `liveModeOptIn` (string or `null`, default: `null`) — must equal EXACTLY the literal `"live"` to
Expand Down
7 changes: 4 additions & 3 deletions apps/loopover-ui/content/docs/ams-kill-switch-incident.mdx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
---
title: Kill-switch incident runbook
description: Detect, activate, and audit a misbehaving Rent-a-Loop miner after the kill-switch stops it — grounded in the shipped #5670 mechanism.
eyebrow: Maintainers
---

Operator runbook for a **misbehaving autonomous miner loop** once the emergency kill-switch has
Expand All @@ -17,9 +18,9 @@ been (or must be) engaged. Grounded in the shipped mechanism from #5670 (`packag

Two independent triggers compose into one scope per repo:

| Trigger | How | Scope reported |
| --- | --- | --- |
| Global | `LOOPOVER_MINER_KILL_SWITCH` env var (`1` / `true` / `yes` / `on`) | `"global"` — every repo |
| Trigger | How | Scope reported |
| -------- | -------------------------------------------------------------------------------- | ------------------------- |
| Global | `LOOPOVER_MINER_KILL_SWITCH` env var (`1` / `true` / `yes` / `on`) | `"global"` — every repo |
| Per-repo | `killSwitch.paused: true` in that repo's `.loopover-miner.yml` (`MinerGoalSpec`) | `"repo"` — that repo only |

A global halt **always wins** and is reported as `"global"` even when the per-repo flag is also set.
Expand Down
7 changes: 4 additions & 3 deletions apps/loopover-ui/content/docs/ams-observability.mdx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
---
title: Observing your miner
description: Point Grafana at redacted AMS reporting exports to see attempt and prediction history without exposing the miner's live local ledgers.
eyebrow: Maintainers
---

How to point Grafana at redacted miner reporting exports to see attempt and prediction history
Expand Down Expand Up @@ -73,9 +74,9 @@ visualize AMS activity, add a dashboard JSON there — or import one at runtime
Ledger` datasources above.

<Callout variant="note">
Panels should query only the redacted reporting schema (e.g. `SELECT * FROM
attempt_log_events`), never a `payload_json`/`reason` column — the exporter drops both, so a
panel referencing them returns no such column.
Panels should query only the redacted reporting schema (e.g. `SELECT * FROM attempt_log_events`),
never a `payload_json`/`reason` column — the exporter drops both, so a panel referencing them
returns no such column.
</Callout>

## Prometheus metrics (opt-in)
Expand Down
25 changes: 11 additions & 14 deletions apps/loopover-ui/content/docs/ams-operations-runbook.mdx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
---
title: AMS operations runbook
description: Recover from SQLite lock contention, ledger corruption, and post-upgrade schema migrations in loopover-miner's local state.
eyebrow: Maintainers
---

Operator-facing runbook for **local SQLite state**: what the concurrency guarantees actually mean,
Expand Down Expand Up @@ -45,7 +46,7 @@ Default **5000 ms**; overridable per-test only — production stores always use
{
title: "Two short-lived writers, same file",
description:
"E.g. a CLI command finishing while loop is idle, or Grafana reading while the miner appends. SQLite waits up to 5 seconds for the lock, then proceeds or surfaces \"database is locked\".",
'E.g. a CLI command finishing while loop is idle, or Grafana reading while the miner appends. SQLite waits up to 5 seconds for the lock, then proceeds or surfaces "database is locked".',
},
{
title: "Append-only ledgers",
Expand All @@ -67,9 +68,8 @@ Default **5000 ms**; overridable per-test only — production stores always use

<Callout variant="warn">
**Invariant:** one active loop (or one intentional writer set) per state directory. Horizontal
scale = **isolated state dirs** — separate compose projects, separate
`LOOPOVER_MINER_CONFIG_DIR`, or the Kubernetes StatefulSet pattern in the [AMS deployment
guide](/docs/ams-deployment).
scale = **isolated state dirs** — separate compose projects, separate `LOOPOVER_MINER_CONFIG_DIR`,
or the Kubernetes StatefulSet pattern in the [AMS deployment guide](/docs/ams-deployment).
</Callout>

Quick health check — `doctor` includes `laptop-state-sqlite` (file exists + readable) and
Expand Down Expand Up @@ -118,9 +118,9 @@ Re-run `loopover-miner doctor`. If locks persist with a single process, see **Le
below.

<Callout variant="note">
Claims are local bookkeeping only. Two miners on different machines claiming the same GitHub
issue is a **fleet coordination** problem (duplicate-cluster adjudication in the engine), not
something SQLite resolves — split state dirs and use operational claim hygiene.
Claims are local bookkeeping only. Two miners on different machines claiming the same GitHub issue
is a **fleet coordination** problem (duplicate-cluster adjudication in the engine), not something
SQLite resolves — split state dirs and use operational claim hygiene.
</Callout>

## Backup and restore
Expand Down Expand Up @@ -158,7 +158,7 @@ loopover-miner doctor --json # verify afterward`}
/>

It also removes any leftover `-wal`/`-shm` sidecar files from the live directory after restoring
each store — those hold in-flight writes from *before* the restore, and leaving them in place
each store — those hold in-flight writes from _before_ the restore, and leaving them in place
would let SQLite silently replay stale pre-restore writes back on top of the freshly restored
file on next open.

Expand All @@ -184,10 +184,7 @@ Check disk space, permissions (`0600` file, `0700` parent), and backup tools cop
**Remediation.** Stop the miner, then back up the whole state directory (even damaged files help
post-mortems):

<CodeBlock
lang="bash"
code={`cp -a "$STATE_DIR" "\${STATE_DIR}.bak.$(date +%Y%m%d%H%M%S)"`}
/>
<CodeBlock lang="bash" code={`cp -a "$STATE_DIR" "\${STATE_DIR}.bak.$(date +%Y%m%d%H%M%S)"`} />

Choose a recovery tier:

Expand Down Expand Up @@ -267,8 +264,8 @@ tarball, pin the previous package version, and file an issue with the failing `u
store filename.

<Callout variant="note" title="Rolling fleet upgrades">
Upgrade and restart **one worker/state dir at a time** so isolated workers never share a
directory mid-migration.
Upgrade and restart **one worker/state dir at a time** so isolated workers never share a directory
mid-migration.
</Callout>

## Related docs
Expand Down
22 changes: 11 additions & 11 deletions apps/loopover-ui/content/docs/ams-sizing.mdx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
---
title: Resource sizing
description: Real, measured CPU/RAM/disk numbers for laptop mode and fleet mode, so an operator can size a host or cluster from data instead of guessing.
eyebrow: Maintainers
---

Real, measured CPU/RAM/disk numbers for laptop mode and fleet mode, so an operator can size a
Expand All @@ -24,13 +25,12 @@ captured.
Both modes measure `loopover-miner init` (laptop mode only) followed by `loopover-miner discover <owner/repo> [<owner/repo>...] --json` against real, small public repositories (`octocat/Hello-World`, `octocat/Spoon-Knife`) with **no `GITHUB_TOKEN`** — real, unauthenticated GitHub GET requests, metadata fan-out + deterministic ranking, no writes.

<Callout variant="note" title="Deliberately excluded: a live attempt cycle">
Sizing a real coding-agent attempt would need an operator-supplied `GITHUB_TOKEN` and
coding-agent CLI credentials, and creates a real branch/PR against a real target repository —
not something to spend for a resource-sizing exercise, and not reproducible by a reviewer
without supplying their own credentials. `discover` is measured instead because it is the
dominant, always-run, network- and CPU-bound phase every mode exercises identically; `attempt`'s
resource profile is dominated by the operator's chosen coding-agent CLI process, not by anything
this package controls.
Sizing a real coding-agent attempt would need an operator-supplied `GITHUB_TOKEN` and coding-agent
CLI credentials, and creates a real branch/PR against a real target repository — not something to
spend for a resource-sizing exercise, and not reproducible by a reviewer without supplying their
own credentials. `discover` is measured instead because it is the dominant, always-run, network-
and CPU-bound phase every mode exercises identically; `attempt`'s resource profile is dominated by
the operator's chosen coding-agent CLI process, not by anything this package controls.
</Callout>

## Methodology
Expand All @@ -56,7 +56,7 @@ Both modes measure `loopover-miner init` (laptop mode only) followed by `loopove
node packages/loopover-miner/bin/loopover-miner.js init --json

LOOPOVER_MINER_CONFIG_DIR=/tmp/sizing-laptop /usr/bin/time -v \\
node packages/loopover-miner/bin/loopover-miner.js discover octocat/Hello-World --dry-run --json
node packages/loopover-miner/bin/loopover-miner.js discover octocat/Hello-World --dry-run --json

du -sh /tmp/sizing-laptop`}
/>
Expand All @@ -70,10 +70,10 @@ names/volumes):**

docker volume create miner-sizing-1
docker run -d --name miner-sizing-1 -v miner-sizing-1:/data/miner --entrypoint sh loopover-miner:sizing \\
-c "loopover-miner discover octocat/Hello-World octocat/Spoon-Knife --json > /tmp/out.json 2>&1; sleep 20"
-c "loopover-miner discover octocat/Hello-World octocat/Spoon-Knife --json > /tmp/out.json 2>&1; sleep 20"

for i in $(seq 1 20); do
docker stats --no-stream --format '{{.Container}}\\t{{.CPUPerc}}\\t{{.MemUsage}}' miner-sizing-1
docker stats --no-stream --format '{{.Container}}\\t{{.CPUPerc}}\\t{{.MemUsage}}' miner-sizing-1
done

docker run --rm -v miner-sizing-1:/data alpine du -ah /data`}
Expand Down Expand Up @@ -118,7 +118,7 @@ docker run --rm -v miner-sizing-1:/data alpine du -ah /data`}
touches the larger per-attempt stores (`attempt-log.sqlite3`, `worktree-allocator.sqlite3`,
etc.), which only grow once real attempts run.
- The 333 MB fleet image is dominated by the Node 24 base image and `node_modules`; `npm prune
--omit=dev` in the Dockerfile already strips dev dependencies from the runtime stage.
--omit=dev` in the Dockerfile already strips dev dependencies from the runtime stage.

See the [AMS deployment guide](/docs/ams-deployment) for the laptop-mode and fleet-mode setup
walkthroughs these numbers apply to, and the [AMS operations
Expand Down
Loading
Loading