llm.py — LLMClient protocol: complete(events, tools, model) -> canonical events, present from the first real LLM call as A5 requires. LiteLLMClient renders the request body via the A10 renderer, posts to the proxy's OpenAI-compatible chat/completions route with the bearer token, and maps provider tool-call IDs to locally-minted canonical IDs at the boundary — a provider ID never enters the log, since every future request is rendered wholly from the canonical record.
RoleMap (A11) — executor/planner/delegate/consultant resolved from NISSE_ROLE_* env vars; pinned() returns the resolved mapping for recording at task start (wired into the orchestrator at build step 5).
Config strictly from env — NISSE_LLM_BASE_URL + NISSE_LLM_TOKEN; envfile.py is a ~15-line .env loader where the real environment always wins. .env is now gitignored, with a committed .env.example documenting every variable (Matrix ones included, ready for the transport PR).
Live smoke test — integration-marked, skips unless the LLM env vars are present. Per operator decision CI stays credential-free; the test runs from the operator's machine once .env exists, and wiring CI secrets later is a five-line change.
httpx added as nisse-dev's HTTP dependency.
13 new unit tests over a mocked HTTP layer: request shape/auth, producing-model attribution, canonical ID minting (provider IDs dropped), HTTP/connection/malformed-response errors, role-map resolution and pinning, env-file semantics. 67 unit + 5 integration green locally, 1 skip (the live test, by design until credentials exist).
PR 2 of the approved steps-3/4 plan.
- **`llm.py`** — `LLMClient` protocol: `complete(events, tools, model) -> canonical events`, present from the first real LLM call as A5 requires. `LiteLLMClient` renders the request body via the A10 renderer, posts to the proxy's OpenAI-compatible `chat/completions` route with the bearer token, and maps provider tool-call IDs to **locally-minted canonical IDs at the boundary** — a provider ID never enters the log, since every future request is rendered wholly from the canonical record.
- **`RoleMap` (A11)** — `executor`/`planner`/`delegate`/`consultant` resolved from `NISSE_ROLE_*` env vars; `pinned()` returns the resolved mapping for recording at task start (wired into the orchestrator at build step 5).
- **Config strictly from env** — `NISSE_LLM_BASE_URL` + `NISSE_LLM_TOKEN`; `envfile.py` is a ~15-line `.env` loader where the real environment always wins. `.env` is now gitignored, with a committed `.env.example` documenting every variable (Matrix ones included, ready for the transport PR).
- **Live smoke test** — integration-marked, skips unless the LLM env vars are present. Per operator decision CI stays credential-free; the test runs from the operator's machine once `.env` exists, and wiring CI secrets later is a five-line change.
- `httpx` added as nisse-dev's HTTP dependency.
13 new unit tests over a mocked HTTP layer: request shape/auth, producing-model attribution, canonical ID minting (provider IDs dropped), HTTP/connection/malformed-response errors, role-map resolution and pinning, env-file semantics. 67 unit + 5 integration green locally, 1 skip (the live test, by design until credentials exist).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
PR 2 of the steps-3/4 plan.
- llm.py: LLMClient protocol — complete(events, tools, model) ->
canonical events — present from the first real LLM call.
LiteLLMClient renders requests via the A10 renderer, targets the
proxy's OpenAI-compatible route, and maps provider tool-call IDs
to locally-minted canonical IDs at the boundary
- RoleMap (A11): executor/planner/delegate/consultant resolved from
env; pinned() returns the mapping to record at task start
- Config strictly from env: NISSE_LLM_BASE_URL, NISSE_LLM_TOKEN;
envfile.py is a minimal .env loader where real env wins; .env now
gitignored with a committed .env.example documenting all variables
(Matrix ones included for the upcoming transport PR)
- Live smoke test against the proxy is integration-marked and skips
without credentials — CI stays credential-free per operator
decision; the test runs locally once .env exists
- httpx added as nisse-dev's HTTP client
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
PR 2 of the approved steps-3/4 plan.
llm.py—LLMClientprotocol:complete(events, tools, model) -> canonical events, present from the first real LLM call as A5 requires.LiteLLMClientrenders the request body via the A10 renderer, posts to the proxy's OpenAI-compatiblechat/completionsroute with the bearer token, and maps provider tool-call IDs to locally-minted canonical IDs at the boundary — a provider ID never enters the log, since every future request is rendered wholly from the canonical record.RoleMap(A11) —executor/planner/delegate/consultantresolved fromNISSE_ROLE_*env vars;pinned()returns the resolved mapping for recording at task start (wired into the orchestrator at build step 5).NISSE_LLM_BASE_URL+NISSE_LLM_TOKEN;envfile.pyis a ~15-line.envloader where the real environment always wins..envis now gitignored, with a committed.env.exampledocumenting every variable (Matrix ones included, ready for the transport PR)..envexists, and wiring CI secrets later is a five-line change.httpxadded as nisse-dev's HTTP dependency.13 new unit tests over a mocked HTTP layer: request shape/auth, producing-model attribution, canonical ID minting (provider IDs dropped), HTTP/connection/malformed-response errors, role-map resolution and pinning, env-file semantics. 67 unit + 5 integration green locally, 1 skip (the live test, by design until credentials exist).
🤖 Generated with Claude Code