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
9 changes: 9 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,15 @@ jobs:
assert m["archetype"]["soul"].strip(), "archetype soul present"
print("manifest ok:", m["id"], "-", len(m["plugins"]), "members")
PY
- name: Watchdog check logic
# The attribution rule in check_model_fallback.py and the verdict rules in
# check_oauth_health.py decide whether #alerts gets woken — a bug there is
# either a silent degrade nobody hears about or a pager that cries wolf, and
# neither shows up in a smoke run against a healthy container. Stdlib
# unittest: CI here is python3 + PyYAML, and a watchdog suite earning a
# dependency install is the wrong trade.
run: python3 -m unittest discover tests -v

- name: README pins match bundle manifest and Dockerfile
# Prevents the member-table and base-image prose from drifting out of sync
# with the canonical pins in protoagent.bundle.yaml and the Dockerfile FROM line.
Expand Down
4 changes: 2 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
# protoAgent core forward on the same image roll — core and member bumps are
# decoupled. Bump this deliberately (and re-verify), keeping it in step with the
# manifest's `verified_against`. Tag format is bare semver (no `v` prefix).
FROM ghcr.io/protolabsai/protoagent:0.137.1
FROM ghcr.io/protolabsai/protoagent:0.145.0

USER root

Expand All @@ -27,7 +27,7 @@ RUN curl -fsSL https://deb.nodesource.com/setup_22.x | bash - \
# Bake the bundle members at their RELEASE TAGS (both public — no build secrets).
# The tags mirror protoagent.bundle.yaml's pins; bump both together (the manifest
# is the source of truth, this bake is its image form).
ARG GITHUB_PLUGIN_REF=v0.3.0
ARG GITHUB_PLUGIN_REF=v0.5.0
RUN git clone --depth 1 --branch "${GITHUB_PLUGIN_REF}" \
https://github.com/protoLabsAI/github-plugin.git /opt/protoagent/plugins/github \
&& rm -rf /opt/protoagent/plugins/github/.git
Expand Down
68 changes: 65 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ python -m server plugin install https://github.com/protoLabsAI/qaEngineer
| Member | Pin | Role |
|---|---|---|
| `workflows` (builtin) | core | the recipe engine the review panels run on |
| [github-plugin](https://github.com/protoLabsAI/github-plugin) | v0.3.0 | the verdict surface — formal Review API tools with CI-terminal + self-review guards inside the tools |
| [github-plugin](https://github.com/protoLabsAI/github-plugin) | v0.5.0 | the verdict surface — formal Review API tools with CI-terminal + self-review guards inside the tools |
| [pr-reviewer-plugin](https://github.com/protoLabsAI/pr-reviewer-plugin) | v0.35.0 | the machinery — webhook chokepoint, structural trigger, panel dispatch, evidence grounding, convergence, approve-on-green sweep, on-demand summon, telemetry + eval |

Persona: [`SOUL.md`](./SOUL.md) (Vera — verdict system, three-layer verification, 80% bar,
Expand Down Expand Up @@ -84,7 +84,44 @@ plus prior-round context. In practice that's **~37k tokens per finder**.
- **No aggressive TPM cap.** A free tier at 12,000 tokens/minute rejects a single finder
outright.
- Routed through a gateway alias, so swapping models is a gateway edit rather than a code
change. Model settings are host-scoped (ADR 0047).
change. Model settings are host-scoped (ADR 0047) — **or** a native subscription lane
(see below), which is not a gateway edit at all.

**The reference host's lane (since 2026-08-23):** `protolabs/smart` on the gateway
(`model.provider: openai`), with `routing.fallback_models: ["protolabs/cloud"]` behind
it. She ran a native Claude subscription (`anthropic-oauth` / `claude-sonnet-5`) from
2026-08-21 to 08-23 and it reviewed well — ~25% faster, same grounding rate — but a
five-finder panel across a fleet's worth of repos **exhausted the subscription's 7-day
quota in under two days** (`anthropic-ratelimit-unified-7d-status: rejected`,
`retry-after: 356343` = 4.1 days). A reviewer that cannot review for four days is not a
reviewer, so the gateway is the sustainable primary and a subscription is the treat.
Set `model.name` and `model.provider` in the SAME config POST either way: they are one
decision (protoAgent#2623).

**The old native-OAuth shape, for reference:** Native OAuth **bypasses the gateway entirely** (ADR 0097) — the fallback alias is
reachable only because protoAgent#2571 lets a namespaced slot name opt out of the native
provider, so `model.api_base` + a gateway key must stay set even though the primary never
uses them. Set `model.name` and `model.provider` in the SAME config POST: they are one
decision (protoAgent#2623), and a native provider paired with a `protolabs/*` name is
rejected on every call.

**A fallback is SILENT.** protoAgent wires langchain's `ModelFallbackMiddleware` raw, and
that middleware swallows the primary's exception with no log, no counter and no event —
found on core 0.144.0 and filed as protoAgent#2956. So a dead subscription doesn't break
the review; it quietly changes which model writes the verdict. That is what
`scripts/check_model_fallback.py` exists to catch, by inference from gateway metrics:
protoAgent-UA traffic arriving at the gateway from Vera's container *is* a fallback,
because her primary never goes there.

**#2956 is FIXED in core 0.145.0**, which the pins above now carry:
`ObservableModelFallbackMiddleware` logs a WARNING and publishes a `model.fallback` bus
event (ADR 0039). The inference script is therefore scheduled for deletion — but not
yet, and the distinction matters: **pinning a version is not running it.** Vera rolls on
watchtower after a merge, so between the pin landing and the roll completing she is on
the old core with no event at all. Retire the script once the running instance reports
0.145.0 *and* the event has been seen firing; deleting the inference before its
replacement is observed working would leave the silent-degrade window covered by
neither.

**Reviews are not cheap.** One structural review is nine LLM steps and 5–9 minutes of
wall clock. On a hosted frontier model that's roughly $0.12–0.15 each; on local inference
Expand Down Expand Up @@ -164,7 +201,7 @@ from the payload):
## Deploying Vera (the reference host)

This repo doubles as Vera's image source: `Dockerfile` = stock protoAgent (**pinned
base** — `protoagent:0.137.1`, in step with the manifest's `verified_against`; bump
base** — `protoagent:0.145.0`, in step with the manifest's `verified_against`; bump
deliberately so a member-pin bump can't drag the core forward on the same roll) +
node/`clawpatch` + the bundle members baked at their manifest pins +
`deploy/vera.langgraph-config.yaml` (seed, not force) + `SOUL.md`.
Expand All @@ -191,6 +228,31 @@ static check can't see a *running* instance whose live config drifted;
`scripts/check_card_drift.py` is the runtime half — point it at the (tailnet-only) card
from the ava fleet cron: `python3 scripts/check_card_drift.py` (exit 1 on drift).

### The watchdogs

Four checks, all run from the ava fleet cron through `scripts/vera-watchdog.sh`, which is
the piece that makes a failure LOUD (a Discord `#alerts` post, with exit 1 = verdict and
exit 2 = unreachable kept as distinct alarms). Every one of them exists because Vera fails
*quietly* — a starved panel, a drifted card, a swapped model underneath a verdict.

| Mode | Check | Asks |
|---|---|---|
| `health` | `check_review_health.py` | is the gate still producing verdicts? (growth in unreviewed/exhausted, completion rate) |
| `drift` | `check_card_drift.py` | does the live card still match the seed? |
| `fallback` | `check_model_fallback.py` | did she silently answer from her fallback model? (gateway-metrics inference — protoAgent#2956) |
| `oauth` | `check_oauth_health.py` | is the subscription credential still signed in, refreshable, and coherent with `model.name`? |

The wrapper runs **installed copies** in `~/.local/bin`, not `scripts/*.py` — this repo is
also the deploy source, so a branch switch would silently disarm a guard that lived inside
it. Re-`install` them after changing a script (there is no auto-update) — the install lines
are in the wrapper's header. That includes **`scripts/vera_api.py`**, the shared
operator-API helper: Python puts a script's own directory on `sys.path[0]`, so a flat
module installed alongside imports cleanly — and a check installed *without* it dies on
ImportError. `python3 -m unittest discover tests` covers the attribution and verdict rules
(CI runs it). It reads `DISCORD_WEBHOOK_ALERTS` from `infisical run`, so an
expired Infisical session downgrades every alarm to a log line nobody reads; that is worth
checking whenever the alerts channel goes quiet for a suspiciously long time.

## Other orgs

Nothing here is protoLabs-specific except the pins and the seed: the bundle installs into
Expand Down
4 changes: 2 additions & 2 deletions protoagent.bundle.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,11 @@ description: >-
own work; never posts a blocking verdict against pending CI.

# The core version this pin set was last verified against (ADR 0049 rule 2).
verified_against: 0.137.1
verified_against: 0.145.0

plugins:
- { id: workflows, builtin: true } # the recipe engine (code-review panels run through it)
- { id: github, url: https://github.com/protoLabsAI/github-plugin, ref: v0.3.0 }
- { id: github, url: https://github.com/protoLabsAI/github-plugin, ref: v0.5.0 }
- { id: pr-reviewer, url: https://github.com/protoLabsAI/pr-reviewer-plugin, ref: v0.35.0 }

enabled: [workflows, github, pr-reviewer] # suggested turn-on list (applied to plugins.enabled)
Expand Down
Loading
Loading