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>
5.3 KiB
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.mdalready 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.mdstates 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:
- 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. - Scope discipline. glas-harness has zero live end-to-end proof yet
(
GLAS-WP-0001-T04is 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. - 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-openweightsdepends onllm-connect's Core +OpenRouterAdapteronly — not the full package surface — confirmed against the actual layer boundaries above, not assumed.GLAS-WP-0001-T06is done.GLAS-WP-0001-T05(bootstraprein-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/ReinWrapperconcept distinct fromReinincontract.py, with an explicit chain-of-responsibility arounddispatch_tool. - A decision on whether
llm-connectitself 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 withllm-connect's own INTENT.md boundary against owning application-level workflow).