Checked ADR-002 part 2's deferred question against the two observability additions that landed since (rein-aharness's tool-event stream, glas-harness's own gateway hub event) -- both turned out simpler as direct implementations, neither needed a wrapping middleware layer. Still zero real candidates for that shape. docs/harness-contract.md gains the Middleware ABC as a documented, unimplemented sketch for if a real third case ever appears -- no code written now. Also flagged GLAS-WP-0002-T02 (live OpenBao verification) as blocked: `bao token lookup` from this workstation returns 403, no usable vault session to provision a new AppRole with. Needs the operator. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
112 lines
5.3 KiB
Markdown
112 lines
5.3 KiB
Markdown
# ADR-002: Credential brokering for rein-openweights; composable reins deferred
|
|
|
|
- Status: accepted (part 1), open (part 2)
|
|
- Date: 2026-07-26
|
|
- Deciders: Bernd Worsch
|
|
|
|
## Context
|
|
|
|
`GLAS-WP-0001-T06` asked who acquires the OpenRouter credential
|
|
`rein-openweights` needs: glas-harness (mirroring how it brokers sand-boxer
|
|
sandbox handles) or the rein itself (mirroring how `rein-aharness` already
|
|
holds its own OpenBao AppRole lane for IMAP/mail credentials).
|
|
|
|
Discussion surfaced a prior question: should `rein-openweights` talk to
|
|
OpenRouter through `llm-connect`, or through a leaner, independent wrapper
|
|
that both `llm-connect` and `rein-openweights` depend on — keeping
|
|
`llm-connect`'s monitoring/evaluation/optimization features optional
|
|
rather than bundled in?
|
|
|
|
Checking `llm-connect` directly resolved that half cleanly:
|
|
|
|
- Its own `ARCHITECTURE-LAYERS.md` already separates Core (frozen adapter
|
|
ABC + models) from Functional (independently shippable per-provider
|
|
adapters, diagnostics, replay, budget tracking — all explicitly
|
|
optional: *"Keeps base install lightweight... optional dep, not
|
|
runtime dep"*) from Configuration (TOML resolution, key lookup).
|
|
- `resolve_api_key()` (`llm_connect/config.py`) only reads an
|
|
**already-materialized** key — explicit argument → env var → file. It
|
|
never talks to OpenBao/ops-warden. `llm-connect/INTENT.md` states this
|
|
as an explicit boundary: *"not intended to... manage secrets,
|
|
credentials, or organizational access policies."*
|
|
|
|
So `llm-connect` was never a credential broker regardless of which
|
|
adapter path `rein-openweights` uses — the earlier concern that "using
|
|
llm-connect" imports a credential model doesn't hold. `rein-openweights`
|
|
can depend on just Core + the `OpenRouterAdapter` Functional module and
|
|
skip diagnostics/replay/budget, the same way any other consumer can
|
|
today.
|
|
|
|
## Decision (part 1 — resolved)
|
|
|
|
**Option B: `rein-openweights` acquires its OpenRouter credential
|
|
directly** (OpenBao/ops-warden), the same way `rein-aharness` already
|
|
does for its own credentials. glas-harness does not broker LLM-provider
|
|
credentials.
|
|
|
|
Rationale, in order of weight:
|
|
|
|
1. Consistency with the existing family precedent. `rein-aharness`'s own
|
|
INTENT.md states *"the harness is the only credential holder and only
|
|
policy enforcement point."* Having glas-harness broker for one rein
|
|
but not the other bakes an inconsistency into the family from its
|
|
second member.
|
|
2. Scope discipline. glas-harness has zero live end-to-end proof yet
|
|
(`GLAS-WP-0001-T04` is a unit-tested wiring, not a live run) — growing
|
|
it into a secrets broker ahead of validated need repeats the
|
|
premature-generalization mistake ADR-001 was written to avoid on the
|
|
sandbox side.
|
|
3. Credential lifecycle doesn't map cleanly onto sand-boxer's
|
|
create/destroy session lifecycle the way a sandbox handle does — the
|
|
analogy that made Option A attractive was weaker than it first looked.
|
|
|
|
Accepted cost: `rein-openweights` duplicates OpenBao wiring rather than
|
|
inheriting it from glas-harness. Revisit centralizing it if a third rein
|
|
repeats the same need — decide from evidence, not in advance.
|
|
|
|
## Consequences (part 1)
|
|
|
|
- `rein-openweights` (`REIN-OW-WP-0001-T04`) implements its own
|
|
credential acquisition; it does not wait on or call into glas-harness
|
|
for this.
|
|
- `rein-openweights` depends on `llm-connect`'s Core + `OpenRouterAdapter`
|
|
only — not the full package surface — confirmed against the actual
|
|
layer boundaries above, not assumed.
|
|
- `GLAS-WP-0001-T06` is done. `GLAS-WP-0001-T05` (bootstrap
|
|
`rein-openweights`) is unblocked.
|
|
|
|
## Part 2 — resolved in ADR-004: composable reins stay deferred
|
|
|
|
See `docs/adr/ADR-004-composable-reins-stay-deferred.md` — the deferral
|
|
below held even after two more observability additions landed; both
|
|
turned out simpler as direct implementations, not middleware.
|
|
|
|
## Part 2 (original framing, for context)
|
|
|
|
Separately, the idea surfaced that monitoring/evaluation/optimization
|
|
(capabilities `llm-connect` already stages as optional Functional
|
|
modules) could eventually become their own **composable reins** —
|
|
cross-cutting capabilities that wrap or observe another rein's
|
|
`dispatch_tool` calls, rather than each being a complete alternative
|
|
harness backend the way `rein-aharness`/`rein-openweights` are.
|
|
|
|
This is a real extension to `docs/harness-contract.md`: the current
|
|
`Rein` ABC assumes one rein *is* the whole session, with no
|
|
wrapping/composition concept (no middleware chain, no way for a
|
|
"monitoring rein" to sit between glas-harness and the base rein's calls).
|
|
|
|
**Left open deliberately** — it does not block `rein-openweights`
|
|
bootstrap and there is only one candidate capability (llm-connect's
|
|
existing optional modules) to generalize from, not evidence of a
|
|
recurring pattern. Revisit if/when a second capability
|
|
(evaluation, or a distinct optimization concern) wants the same
|
|
treatment. If pursued, it would need:
|
|
|
|
- A `Middleware`/`ReinWrapper` concept distinct from `Rein` in
|
|
`contract.py`, with an explicit chain-of-responsibility around
|
|
`dispatch_tool`.
|
|
- A decision on whether `llm-connect` itself gets decomposed into
|
|
reins/middleware, or stays a shared library that both base reins and
|
|
middleware reins happen to depend on (the leaner option, and the one
|
|
consistent with `llm-connect`'s own INTENT.md boundary against owning
|
|
application-level workflow).
|