glas-harness/docs/adr/ADR-002-credential-brokering-and-composable-reins.md
tegwick 9bbff9d336
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
ADR-004: composable reins as middleware stays deferred (GLAS-WP-0002-T01)
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>
2026-07-26 20:22:55 +02:00

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.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).