Compare commits
2 Commits
b055c8d7bb
...
eaf4a955af
| Author | SHA1 | Date | |
|---|---|---|---|
| eaf4a955af | |||
| e9dc9a8517 |
34
CLAUDE.md
Normal file
34
CLAUDE.md
Normal file
@@ -0,0 +1,34 @@
|
||||
# Markitect — Claude Code Instructions
|
||||
|
||||
## Custodian State Hub Integration
|
||||
|
||||
This project is tracked as the **markitect** domain in the Custodian State Hub.
|
||||
Hub topic ID: `5571d954-0d30-4950-980d-7bcaaad8e3e2`
|
||||
|
||||
### Session Protocol
|
||||
|
||||
**At the start of every session:**
|
||||
Call `get_state_summary()` via the `state-hub` MCP tool to orient yourself.
|
||||
If the hub is not reachable, start it: `cd ~/the-custodian/state-hub && make api`
|
||||
|
||||
**At the end of every session:**
|
||||
Call `add_progress_event()` with at minimum:
|
||||
- `topic_id`: `5571d954-0d30-4950-980d-7bcaaad8e3e2`
|
||||
- `summary`: what was accomplished or left in-flight
|
||||
- `event_type`: `note` for routine updates, `milestone` for completions, `blocker` for blockers
|
||||
|
||||
### Available State-Hub MCP Tools
|
||||
|
||||
- `get_state_summary()` — full cross-domain overview
|
||||
- `add_progress_event(summary, topic_id, event_type, detail)` — log progress
|
||||
- `create_workstream(topic_id, title, ...)` — create a new workstream
|
||||
- `create_task(workstream_id, title, ...)` — create a task under a workstream
|
||||
- `update_task_status(task_id, status)` — move task through lifecycle
|
||||
- `record_decision(title, decision_type, topic_id, ...)` — log decisions
|
||||
- `resolve_decision(decision_id, rationale, decided_by)` — close a decision
|
||||
|
||||
### If the MCP Server is Not Available
|
||||
|
||||
The state-hub MCP server (`state-hub`) is registered at user scope in `~/.claude.json`.
|
||||
It requires the API to be running at `http://127.0.0.1:8000`.
|
||||
Fallback: use `curl` directly against the REST API — see `/docs` at the hub URL.
|
||||
214
roadmap/llm-shared-library/PLAN.md
Normal file
214
roadmap/llm-shared-library/PLAN.md
Normal file
@@ -0,0 +1,214 @@
|
||||
# LLM Adapter Layer — Extract as Shared Library
|
||||
|
||||
## Vision
|
||||
|
||||
The `markitect.llm` module is a clean, stdlib-only adapter layer for calling
|
||||
LLMs via OpenRouter, Gemini, OpenAI, and the Claude Code CLI. It implements a
|
||||
uniform interface, a 7-layer TOML config chain, embedding support with caching,
|
||||
and typed exceptions. It should be usable by all projects in the Bernd Worsch
|
||||
ecosystem without pulling in all of markitect.
|
||||
|
||||
This roadmap tracks extracting it into a standalone installable library.
|
||||
|
||||
---
|
||||
|
||||
## Current State
|
||||
|
||||
The module lives at `markitect/llm/` (~16 files, ~1500 LOC, stdlib-only) and
|
||||
provides:
|
||||
- **4 text adapters**: OpenRouter, Gemini, OpenAI, Claude Code CLI
|
||||
- **2 embedding adapters**: OpenAI-compatible (OpenAI + OpenRouter)
|
||||
- **Embedding cache**: JSON-backed, content-digest validated
|
||||
- **Similarity utilities**: pure-Python cosine similarity, matrix, pair-finding
|
||||
- **7-layer TOML config chain**: CLI > env > user/dir preference/default > hardcoded
|
||||
- **Typed exceptions**: LLMError hierarchy
|
||||
- **HTTP wrapper**: urllib-only, typed exception translation
|
||||
|
||||
### Two Coupling Issues Blocking Clean Extraction
|
||||
|
||||
| Issue | Location | Severity |
|
||||
|-------|----------|----------|
|
||||
| `RunConfig` and `LLMResponse` are defined in `markitect.prompts.execution.models`, not in `markitect.llm` | `markitect/prompts/execution/models.py` | High — creates cross-module import for all consumers |
|
||||
| TOML config chain hardcodes `"markitect"` as app name (paths: `~/.config/markitect/`, env prefix `MARKITECT_`, files: `.markitect.toml`) | `markitect/llm/toml_config.py` | Medium — consumers either accept markitect config or can't use the chain |
|
||||
|
||||
---
|
||||
|
||||
## Terminology
|
||||
|
||||
- **adapter**: concrete implementation of `LLMAdapter` for a single provider
|
||||
- **factory**: `create_adapter()` / `create_embedding_adapter()` — provider-agnostic entry points
|
||||
- **config chain**: 7-layer resolution of provider + model (CLI → env → TOML → hardcoded)
|
||||
- **standalone library**: a Python package installable with `pip install` from a git URL or local path, without PyPI
|
||||
- **consumer**: any project that imports and uses the library (markitect itself, custodian, railiance, etc.)
|
||||
|
||||
---
|
||||
|
||||
## Packaging Decision (Pending)
|
||||
|
||||
Before Phase 2 starts, one architectural decision must be resolved:
|
||||
|
||||
> **D1: Where does the extracted library live?**
|
||||
>
|
||||
> **Option A — Standalone repo** (`~/bw-llm` or similar):
|
||||
> - Clean separation, versioned independently, installable via `pip install git+file:///...` or git URL
|
||||
> - Adds a repo to maintain; changes require bumping version in dependents
|
||||
>
|
||||
> **Option B — Subfolder of markitect with own `pyproject.toml`** (monorepo-lite):
|
||||
> - Stays co-located with the main codebase that will use it most
|
||||
> - Less friction for iteration; single git history
|
||||
> - Slightly unorthodox but valid for personal infrastructure
|
||||
>
|
||||
> **Option C — Just `pip install markitect` in other projects**:
|
||||
> - Zero extraction work; reuse today
|
||||
> - Pulls all of markitect (prompts, infospace, CLI, etc.) as transitive deps
|
||||
> - Acceptable short-term if other projects are small
|
||||
|
||||
---
|
||||
|
||||
## Stages
|
||||
|
||||
### Stage 1 — Decouple (within markitect)
|
||||
|
||||
Prepare the module for extraction without changing its public API.
|
||||
|
||||
#### S1.1 — Move RunConfig + LLMResponse into markitect.llm
|
||||
|
||||
`RunConfig` and `LLMResponse` are currently in `markitect.prompts.execution.models`.
|
||||
The LLM adapters import from there, creating a hard dependency on the prompt system.
|
||||
|
||||
**Work:**
|
||||
- Move both dataclasses to `markitect/llm/models.py`
|
||||
- Update all imports in `markitect.llm` and `markitect.prompts`
|
||||
- Keep a re-export shim in `markitect.prompts.execution.models` for backwards compat
|
||||
|
||||
**Acceptance:** `markitect/llm/` has zero imports from `markitect.prompts.*`
|
||||
|
||||
#### S1.2 — Parameterize the TOML config chain
|
||||
|
||||
Replace the hardcoded `"markitect"` app name with a configurable `app_name` parameter.
|
||||
|
||||
**Work:**
|
||||
- Add `app_name: str = "markitect"` parameter to `resolve_llm()` and the config
|
||||
path helpers in `toml_config.py`
|
||||
- Derive config file path (`~/.config/{app_name}/config.toml`), env prefix
|
||||
(`{APP_NAME}_HELPER_MODEL`), and local config file (`.{app_name}.toml`) from it
|
||||
- All existing behaviour is preserved when `app_name="markitect"` (default)
|
||||
|
||||
**Acceptance:** A consumer can call `resolve_llm(app_name="railiance")` and get
|
||||
config from `~/.config/railiance/config.toml` and `RAILIANCE_HELPER_MODEL`.
|
||||
|
||||
#### S1.3 — Isolation tests
|
||||
|
||||
Write a test file that imports only from `markitect.llm.*` and verifies no
|
||||
accidental coupling remains.
|
||||
|
||||
**Acceptance:** `pytest tests/test_llm_isolation.py` passes; no import of
|
||||
`markitect.prompts` or `markitect.infospace` in the LLM module tree.
|
||||
|
||||
---
|
||||
|
||||
### Stage 2 — Extract
|
||||
|
||||
#### S2.1 — Resolve D1: packaging location
|
||||
|
||||
Record the decision and create the package scaffold.
|
||||
|
||||
**Acceptance:** D1 resolved, `pyproject.toml` for the library exists at the
|
||||
chosen location with name, version `0.1.0`, and declared dependencies.
|
||||
|
||||
#### S2.2 — Create standalone package
|
||||
|
||||
Move (or symlink) the llm module into the new package structure. Wire up
|
||||
the `pyproject.toml` entry points. Verify `pip install -e <path>` works.
|
||||
|
||||
**Files to carry over:**
|
||||
```
|
||||
llm/
|
||||
__init__.py # re-exports: create_adapter, create_embedding_adapter,
|
||||
# LLMAdapter, EmbeddingAdapter, LLMConfig, exceptions
|
||||
models.py # RunConfig, LLMResponse (moved from S1.1)
|
||||
config.py # load_config, resolve_api_key
|
||||
toml_config.py # resolve_llm (parameterized from S1.2)
|
||||
factory.py # create_adapter
|
||||
exceptions.py # LLM exception hierarchy
|
||||
openrouter.py
|
||||
claude_code.py
|
||||
gemini.py
|
||||
openai.py
|
||||
embedding_adapter.py
|
||||
embedding_openai.py
|
||||
embedding_factory.py # create_embedding_adapter
|
||||
embedding_cache.py
|
||||
similarity.py
|
||||
_http.py
|
||||
_token_estimator.py
|
||||
```
|
||||
|
||||
**Acceptance:** `python -c "from bw_llm import create_adapter; print('ok')"` works
|
||||
in a fresh venv with only the new package installed.
|
||||
|
||||
#### S2.3 — Update markitect to depend on extracted package
|
||||
|
||||
Replace `markitect/llm/` with an import alias pointing to the new package, or
|
||||
add the package as a path dependency in markitect's `pyproject.toml`.
|
||||
|
||||
**Acceptance:** All markitect tests pass; `markitect/llm/__init__.py` is either
|
||||
removed or becomes a thin re-export of `bw_llm`.
|
||||
|
||||
#### S2.4 — Integration smoke test
|
||||
|
||||
Run the full markitect infospace pipeline (entity extraction + evaluation) end-to-end
|
||||
against a small fixture to confirm nothing broke.
|
||||
|
||||
**Acceptance:** `markitect infospace evaluate --dry-run` succeeds on a 3-entity fixture.
|
||||
|
||||
---
|
||||
|
||||
### Stage 3 — Adopt in First Consumer
|
||||
|
||||
#### S3.1 — Integrate in one other project
|
||||
|
||||
Pick the first real consumer (likely the custodian state-hub, for LLM-assisted
|
||||
state summaries or decision rationale generation) and wire up the library.
|
||||
|
||||
**Work:**
|
||||
- Add `bw-llm` (or equivalent) as a dependency
|
||||
- Write a small usage example (e.g., `llm_helper.py`)
|
||||
- Confirm config chain works with the consumer's own app name
|
||||
|
||||
#### S3.2 — Usage guide
|
||||
|
||||
Write `README.md` for the library covering:
|
||||
- Installation (local path / git URL)
|
||||
- Supported providers and env vars
|
||||
- TOML config file locations and format
|
||||
- `create_adapter()` / `create_embedding_adapter()` quick-start
|
||||
- Error handling
|
||||
|
||||
**Acceptance:** Another developer (or agent) can follow the README to use the library
|
||||
in a new project without reading source code.
|
||||
|
||||
---
|
||||
|
||||
## Stage Summary
|
||||
|
||||
| Stage | Description | Key Deliverable | Blocks |
|
||||
|-------|-------------|-----------------|--------|
|
||||
| S1.1 | Move RunConfig/LLMResponse to llm | Zero cross-module deps | S2.2 |
|
||||
| S1.2 | Parameterize app name | Configurable config chain | S2.2 |
|
||||
| S1.3 | Isolation tests | Green test suite | S2.1 |
|
||||
| S2.1 | Resolve packaging decision (D1) | pyproject.toml scaffold | S2.2 |
|
||||
| S2.2 | Create standalone package | `pip install` works | S2.3 |
|
||||
| S2.3 | Update markitect | markitect uses extracted lib | S2.4 |
|
||||
| S2.4 | Integration smoke test | Full pipeline passes | S3.1 |
|
||||
| S3.1 | First consumer integration | Library used in real project | S3.2 |
|
||||
| S3.2 | Usage guide | README published | — |
|
||||
|
||||
---
|
||||
|
||||
## Out of Scope
|
||||
|
||||
- Publishing to PyPI (unnecessary for personal infrastructure; git/local installs suffice)
|
||||
- Adding new LLM providers (separate concern)
|
||||
- Porting the helper CLI to the library (the CLI is markitect-specific)
|
||||
- Async adapters (current sync interface is sufficient; can be added later)
|
||||
Reference in New Issue
Block a user