Migrate all repos into monorepo context folders

Bahn: aisupport, Analyse-O2C-C2S, awesome-bahn-mcp-servers, beam-mcp,
      Confluence_Bot, db-planet-mcp-server, O2C-Harness, project-audit,
      Projekt-KIQ-HP, teamlandkarte-mcp
Dhive: Jury-Voting
Privat: CV, NoteGraph (NOTE: NoteGraph needs complete redo after consolidation)
Shared: AI-Orchestrator, OrgMyLife, power_skills_and_more
Shared/references: symphony (read-only)

Bahn repos remain available as independent remotes - this monorepo
pulls them in via subtree, the originals are untouched.
This commit is contained in:
2026-06-30 20:39:52 +02:00
parent 2f2b295531
commit a5f8fb49ab
1717 changed files with 447332 additions and 0 deletions
@@ -0,0 +1,135 @@
# Design: Capacity→Task Matching and Embedding-based Inference
## Context
The system was originally optimized for task→capacity matching and relied on Azure chat completions for role/requirements extraction.
To enable a symmetrical workflow and reduce latency/cost, this change:
- adds tools to browse capacities and match a capacity against open tasks
- reworks role and competence inference to use embeddings only (Data Lake vocabularies)
- preloads and caches role/competence vocab embeddings on startup (with runtime on-demand fallback)
- preloads and caches task text embeddings by `task_id`
## Key decisions
### A. Vocabulary-driven inference (roles + competences)
- Maintain two vocabularies sourced from the Data Lake:
- roles: `teamlandkarte_v_capacity_roles_latest` filtered by `active=true` and `staffing_board_relevant=true`
- competences: unique values from `beschaffungstool_kmp_skill_latest.skillname__c`
- Generate embeddings for these vocabularies and store them in the local SQLite embedding cache.
### B. Task text embedding stored by task_id
To minimize embedding transaction costs for repeated analysis of the same task:
- build `task_text = f"{title}\n\n{description}"` (title optional)
- embed `task_text` once
- store that embedding under a stable key derived from `task_id` (and embedding model/dims)
**Explicit cache key formats**:
```python
# Role vocabulary entries
key = f"role_vocab:{model}:{dims}:{normalized_role_name}"
# Competence vocabulary entries
key = f"comp_vocab:{model}:{dims}:{normalized_competence_name}"
# Task text embeddings by task_id
key = f"task_id:{model}:{dims}:{task_id}"
```
Where:
- `model` = embedding model name (e.g., `text-embedding-3-large`)
- `dims` = embedding dimensions (e.g., `3072`)
- `normalized_role_name` = normalized role text (trimmed, whitespace-collapsed, lowercased)
- `normalized_competence_name` = normalized competence text (trimmed, whitespace-collapsed, lowercased)
- `task_id` = database task ID (string or integer, converted to string)
### C. Inference logic (embedding similarity)
- Role inference:
- compute cosine similarity between `task_text_embedding` and all role vocabulary embeddings
- return the single best matching role with similarity score as a Markdown table: `Role | Similarity`
- Competence inference (for validation and display):
- compute cosine similarity between `task_text_embedding` and all competence vocabulary embeddings
- return top-N competences with similarity scores as a Markdown table: `Competence | Similarity` (descending order)
**Configuration** (under `[matching.inference]`):
```toml
[matching.inference]
max_competences = 8 # default: 8; omit to disable limit (no max)
min_similarity = 0.4 # default: 0.4; omit to disable threshold filtering
```
**Configuration rules**:
- If `max_competences` is not specified: no limit on number of competences returned.
- If `min_similarity` is not specified: no threshold filtering applied (include all).
- At least one of `max_competences` or `min_similarity` MUST be specified in config to avoid unbounded results.
- Selection rule: if both are set, return top `max_competences` among those with similarity `>= min_similarity`.
### D. Component structure for vocabulary preload
**Component: VocabularyCache**
- **Location**: `src/teamlandkarte_mcp/matching/vocabulary.py` (new module)
- **Responsibilities**:
- Fetch role + competence vocabularies from Data Lake via database client
- Normalize and deduplicate vocabulary entries
- Embed missing entries using batch + chunking via `SimilarityEngine.prefetch_embeddings(...)`
- Store embeddings in SQLite cache with stable keys
- Provide in-memory memoization for fast repeated inference during tool execution
- **Initialization**: Called from `build_server()` after DB client + similarity engine initialization
- **Refresh strategy**: On startup only; no TTL-based refresh during runtime
**Task embedding preload**:
- Preload **all open tasks** on startup (no limit).
- Build `task_text = title + "\n\n" + description` for each task (title optional).
- Embed only cache-missing `task_id` vectors (check SQLite cache first).
- Store into SQLite embedding cache with key format: `task_id:{model}:{dims}:{task_id}`.
- Runtime fallback: if a task embedding is missing during tool execution, embed on-demand (strict semantics).
### E. Determinism and strict failure semantics
- Runs are deterministic given fixed models and cached embeddings.
- Azure embedding failures fail the operation immediately.
- The existing global embedding improvements (dedup + batch + layered caches) should be reused.
## Data flow
### 1) Vocabulary refresh
1. fetch role list + competence list from Data Lake
2. normalize/deduplicate
3. embed missing entries via Azure batch API
4. store embeddings in SQLite cache
### 2) Task validation
1. fetch task (title+description) + DB skills
2. compute/retrieve `task_text_embedding` by task_id
3. infer:
- top-K roles
- top-K competences
4. compare:
- DB skills vs inferred competences (plus similarity thresholds)
- dates (unchanged)
### 3) Capacity→task matching
1. load the target capacity (role + competences + availability)
2. list open tasks
3. compute/retrieve `task_text_embedding` for each task
4. compute overall similarity (role + competences) between the capacity and each task
5. categorize + return table-first results (same output conventions as existing matching tools)
## Alternatives considered
- Keep chat API for role inference: rejected (latency/cost + redundant).
- Use raw string matching for vocab mapping: rejected (less robust than embeddings).
## Risks
- Vocabulary size (competences) may be large → requires batch embedding + caching and careful refresh strategy.
- Model updates can change nearest neighbors → mitigate with pinned model/deployment and cache key including model/dims.
@@ -0,0 +1,82 @@
# Change: Add capacity→task matching tools and embedding-based inference
## Why
The server currently supports **task→capacity matching** (`list_open_tasks`, `get_task_details`, `find_matching_capacities`, `find_capacities_for_task`). There is no symmetrical workflow to start from a capacity/person and discover relevant tasks.
Additionally, role/requirements inference and task validation still rely on the Azure chat API. This introduces latency/cost, complicates failure semantics, and is redundant given the system already uses embeddings + cosine similarity for matching.
## What changes
### New MCP tools
- `list_free_capacities(limit=20)`
- Show the X most recent active capacities in a table-first Markdown output.
- `get_capacity_details(capacity_id)`
- Show a single capacity/person entry (same fields as the list, table-first).
- `find_matching_tasks(capacity_id)`
- Match a capacity against all open tasks based on role + competences.
- Returns paginated Markdown table with task_id, title, required competences, availability, score, and category.
### Rework role + competence inference (remove chat API)
- Replace chat-based role inference with **embedding-based role inference**:
- infer roles from a combined text: **task title (if present) + task description**
- embed this combined text once and match against a pre-embedded **role vocabulary** from the Data Lake.
- Replace chat-based competence extraction/validation with **embedding-based competence matching**:
- embed the task text and match against a pre-embedded **competence vocabulary** from the Data Lake.
- Store the embedding for `task_id` (combined title+description text) in the local SQLite embedding cache to minimize repeated embedding costs.
### Data Lake sourcing
- Competence vocabulary: `hive.tier1_open_lake.beschaffungstool_kmp_skill_latest` (existing source)
- Role vocabulary: `teamlandkarte_v_capacity_roles_latest` via columns:
- role name column: `name`
- filters: `active=true` and `staffing_board_relevant=true`
### Azure OpenAI usage
- Azure embeddings remain required.
- Azure chat API usage is removed completely once all dependent features are migrated.
## Impact
- **MCP API surface**: Adds 3 tools (capacity browsing + capacity→task matching).
- **Business logic**: TaskAnalyzer and validation logic migrate to embeddings-only.
- **Data access**: Requires new read queries to fetch role/competence vocabularies.
- **Caching**: Reuses existing SQLite embedding cache; adds stable keys for task_id text embeddings.
- **Docs**: README, assistant prompt, and architecture must be updated.
## Non-goals
- Changing scoring weights or category thresholds.
- Building a full-text search for tasks/capacities.
## Open questions (need confirmation)
1. **“Most recent capacities”**: should ordering be by `begin_date` descending, by `end_date` descending, or by a DB “created/updated” timestamp (if available)?
2. `list_free_capacities` “owner/team”: do you want **owner_name only**, or both `owner_name` and an explicit `team` field (if available in the view)?
3. Do we keep the existing `infer_roles(...)` tool name/shape, but change its implementation to embeddings-only (recommended), or introduce a new tool and deprecate the old one?
4. For embedding-based competence inference for validation: should the output be a **ranked list with similarity scores**, or a **binary chosen set** (top-N)? If top-N, what default N?
---
## Confirmed decisions
- `list_free_capacities`: order by **most recent `creation_date`**.
- Capacity “Owner/Team”: use the `owner_name` column (contains the **team name**).
- Role inference tool:
- introduce a **new tool** and deprecate `infer_roles`
- the new tool returns only the **single closest role** (no rank/rationale)
- Competence inference:
- return a maximum of **X** competences (**configurable**, default: 8)
- apply a configurable **similarity score threshold** (default: 0.4)
- if `max_competences` is not specified: no limit on number of competences
- if `min_similarity` is not specified: no threshold filtering
- at least one parameter must be specified to avoid unbounded results
- if both are set: select **top X** among those **>= threshold**
- Refresh strategy:
- on startup only
- only cache-missing roles/competences are embedded
- embed **all open tasks** (combined title+description) on startup
- if something is still missing at runtime, embed on-demand (strict semantics)
@@ -0,0 +1,150 @@
# Spec Delta: Capacity→Task Matc### Requirement: Match tasks### Requirement: Role inference tool is embe### Requirement: Task validation uses embedding-based role and competence inference
Task validation SHALL use embedding similarity against role and competence vocabularies and SHALL NOT call the Azure chat API. Output SHALL include Markdown tables for inferred roles and competences with similarity scores.
#### Scenario: Validate task requirements without chat calls
- **WHEN** the client calls `validate_task_requirements(task_id=...)`
- **THEN** the server infers the task's primary role via the role vocabulary (embeddings)
- **AND** the output includes a Markdown table with columns `Role | Similarity` (single row)
- **AND** the server infers competences via the competence vocabulary (embeddings) honoring configured max/threshold
- **AND** the output includes a Markdown table with columns `Competence | Similarity` in descending order by similarity
- **AND** the output remains table-first and includes inferred role/competences with similarity scoresy and returns a single closest role
Role inference SHALL be based on embedding similarity between a task's combined text and the Data Lake role vocabulary and SHALL return only the single closest role as a Markdown table.
#### Scenario: Infer primary role from task_id or task_text
- **WHEN** the client calls `infer_primary_role(task_id=...)`
- **THEN** the system builds a combined text consisting of the task title (if present) and the task description
- **AND** the combined text is embedded and compared against the available role list
- **AND** the output is a Markdown table with columns `Role | Similarity` containing the single best matching role
- **WHEN** the client calls `infer_primary_role(task_text=...)`
- **THEN** the system embeds the provided text and compares it against the available role list
- **AND** the output is a Markdown table with columns `Role | Similarity` containing the single best matching role
- **AND** exactly one of `task_id` or `task_text` MUST be provided
- **AND** the legacy `infer_roles(...)` tool MUST be removed completelyThe system SHALL expose a tool `find_matching_tasks(capacity_id)` that matches a capacity against all published tasks using role + competence similarity.
#### Scenario: Match tasks for a capacity using role + competence similarity
- **GIVEN** a capacity has a role name and a competence list
- **AND** at least one open/published task exists in the Data Lake
- **WHEN** the client calls `find_matching_tasks(capacity_id=<id>)`
- **THEN** the server infers each task's primary role before computing similarity
- **AND** the server infers each task's competences using embedding similarity with configured max/threshold
- **AND** the server returns a Markdown table with columns: `task_id`, `Title`, `Required Competences`, `Availability`, `Score`, `Category`
- **AND** results are sorted by similarity score in descending order
- **AND** the output structure matches `find_matching_capacities` (summary counts table + results table)
- **AND** the server returns categorized results (Top/Good/Partial/Low) using the same conventions as `find_matching_capacities`+ Embedding-based Inference
## ADDED Requirements
### Requirement: List free capacities
The system SHALL expose a tool `list_free_capacities(limit=20)` to list the most recent active capacities in a table-first Markdown format.
#### Scenario: List free capacities ordered by creation_date
- **WHEN** the client calls `list_free_capacities(limit=3)`
- **THEN** the server returns a Markdown table with columns:
- `capacity_id`
- `Owner/Team` (sourced from the capacitys `owner_name` column)
- `Role`
- `Competences`
- `Availability`
- **AND** the entries are ordered by `creation_date` descending
- **AND** `Competences` is rendered as a comma-separated list (or `(none)`)
- **AND** `Availability` is rendered as `begin_date .. end_date` (using `(open)` when the end date is missing)
- **AND** when there are zero results, the output still contains a valid Markdown table (a dummy row is acceptable)
### Requirement: Get capacity details
The system SHALL expose a tool `get_capacity_details(capacity_id)` that returns a single capacity entry in the same table-first format as `list_free_capacities`.
#### Scenario: Get capacity details for a known capacity_id
- **WHEN** the client calls `get_capacity_details(capacity_id=<id>)`
- **THEN** the server returns a table-first Markdown output containing the same fields as `list_free_capacities`
- **AND** the output MAY include a “Next steps” section pointing to `find_matching_tasks(capacity_id=...)`
### Requirement: Match tasks for a capacity
The system SHALL expose a tool `find_matching_tasks(capacity_id, ...)` that matches a capacity against all published tasks using role + competence similarity.
#### Scenario: Match tasks for a capacity using role + competence similarity
- **GIVEN** a capacity has a role name and a competence list
- **AND** at least one open/published task exists in the Data Lake
- **WHEN** the client calls `find_matching_tasks(capacity_id=<id>)`
- **THEN** the server infers each tasks primary role before computing similarity
- **AND** the server returns categorized results (Top/Good/Partial/Low) using the same conventions as `find_matching_capacities`
## MODIFIED Requirements
### Requirement: Role inference tool is embeddings-only and returns a single closest role
Role inference SHALL be based on embedding similarity between a tasks combined text and the Data Lake role vocabulary and SHALL return only the single closest role.
#### Scenario: Infer primary role from task_id or task_text
- **WHEN** the client calls `infer_primary_role(task_id=...)`
- **THEN** the system builds a combined text consisting of the task title (if present) and the task description
- **AND** the combined text is embedded and compared against the available role list
- **AND** the output is the single best matching role
- **WHEN** the client calls `infer_primary_role(task_text=...)`
- **THEN** the system embeds the provided text and compares it against the available role list
- **AND** the output is the single best matching role
- **AND** exactly one of `task_id` or `task_text` MUST be provided
- **AND** the legacy `infer_roles(...)` tool MUST be removed completely
### Requirement: Competence inference is configurable by max count and threshold
Competence inference for validation SHALL be derived via embedding similarity against the competence vocabulary and MUST support limiting by both maximum count and a minimum similarity threshold. Output SHALL be a Markdown table.
#### Scenario: Select top X competences above threshold
- **GIVEN** `max_competences = X` and `min_similarity = T` are configured
- **WHEN** competences are inferred for a task
- **THEN** only competences with similarity `>= T` are considered
- **AND** from those, the top `X` competences by similarity are returned
- **AND** the output is a Markdown table with columns `Competence | Similarity` in descending order by similarity
### Requirement: Task validation uses embedding-based role and competence inference
Task validation SHALL use embedding similarity against role and competence vocabularies and SHALL NOT call the Azure chat API.
#### Scenario: Validate task requirements without chat calls
- **WHEN** the client calls `validate_task_requirements(task_id=...)`
- **THEN** the server infers the tasks primary role via the role vocabulary (embeddings)
- **AND** the server infers competences via the competence vocabulary (embeddings) honoring configured max/threshold
- **AND** the output remains table-first and includes inferred role/competences with similarity scores
### Requirement: Startup-only embedding preload with runtime fallback
The server SHOULD preload embeddings on startup to reduce runtime Azure calls, but MUST still embed on-demand for any cache misses encountered during tool execution.
#### Scenario: Preload only cache-missing embeddings and fall back at runtime
- **WHEN** the server starts
- **THEN** it fetches role vocabulary + competence vocabulary from the Data Lake
- **AND** it embeds only cache-missing role/competence vocabulary entries
- **AND** it fetches **all open tasks** (no limit)
- **AND** it builds combined task text (`title + "\n\n" + description`) and embeds only cache-missing task text embeddings keyed by task_id
- **AND** during runtime, if an embedding is still missing, the server retrieves it on-demand (strict semantics)
## REMOVED Requirements
### Requirement: Chat-based extraction for roles and competences
The system SHALL NOT require Azure chat completions for role inference or task validation once this change is implemented.
#### Scenario: Azure chat API is not required for matching and validation
- **WHEN** a matching or validation tool is executed
- **THEN** no Azure chat completion request is made
- **AND** only Azure embeddings are used where LLM intelligence is required
@@ -0,0 +1,149 @@
# Tasks: Add capacity→task matching tools and embedding-based inference
> Change: `add-capacity-to-task-matching-tools`
>
> Status: **Implemented**
## Phase 1: Requirements + Configuration decisions
- [x] 1.1 Define `list_free_capacities` ordering precisely:
- order by most recent `creation_date` (descending)
- [x] 1.2 Confirm “Owner/Team” display:
- use `owner_name` (contains the team name)
- [x] 1.3 Introduce a new role inference tool and remove the old:
- add `infer_primary_role(task_id? | task_text?)` that returns the single closest role as a Markdown table with `Role | Similarity` columns
- remove `infer_roles(...)` MCP tool completely (no deprecation period)
- remove `TaskAnalyzer.extract_ranked_roles()` method completely
- update `extract_requirements()` and other callers to use new primary-role inference
- [x] 1.4 Add configuration for competence inference:
- `matching.inference.max_competences` (int, default: 8; if not specified, no limit)
- `matching.inference.min_similarity` (float in [0,1], default: 0.4; if not specified, no threshold)
- at least one parameter MUST be specified to avoid unbounded results
- selection rule: pick top X among those >= threshold when both are set
- [x] 1.5 Define startup-only refresh behavior:
- on startup embed only cache-missing role/competence vocab entries
- on startup embed **all open tasks** (title+description by task_id)
- at runtime still embed on-demand for misses (strict failure semantics)
## Phase 2: Data Lake queries (roles, competences, capacities)
- [x] 2.0 Verify Data Lake schema:
- confirm `teamlandkarte_v_capacity_roles_latest` has columns: `name`, `active`, `staffing_board_relevant`
- confirm capacities view has `creation_date` column
- [x] 2.1 Add `TrinoClient.get_recent_free_capacities(limit)` query returning:
- `capacity_id`, `owner_name` (team), `role_name`, `creation_date`, `begin_date`, `end_date`, `competences[]`
- ordering: `creation_date DESC`
- [x] 2.2 Add `TrinoClient.get_capacity_by_id(capacity_id)` query returning the same fields.
- [x] 2.3 Add `TrinoClient.get_all_role_names()`:
- source: `teamlandkarte_v_capacity_roles_latest`
- filter: `active=true AND staffing_board_relevant=true`
- return unique, non-empty role names
- [x] 2.4 Add `TrinoClient.get_all_competence_names()`:
- source: `beschaffungstool_kmp_skill_latest.skillname__c`
- return unique, non-empty competence names
- [x] 2.5 Add unit tests for SQL read-only guard + query shape (where applicable).
## Phase 3: Startup embedding preload (vocabularies + tasks)
- [x] 3.1 Define stable cache-key scheme (explicit format as documented in design.md):
- role vocab entries: `f"role_vocab:{model}:{dims}:{normalized_role_name}"`
- competence vocab entries: `f"comp_vocab:{model}:{dims}:{normalized_competence_name}"`
- task text embeddings by task_id: `f"task_id:{model}:{dims}:{task_id}"`
- [x] 3.2 Implement `VocabularyCache` component (`src/teamlandkarte_mcp/matching/vocabulary.py`):
- fetch role vocabulary + competence vocabulary from Trino
- normalize/deduplicate
- embed only cache-missing entries (batch + chunking via `SimilarityEngine.prefetch_embeddings(...)`)
- store into SQLite embedding cache
- keep in-memory memoization for fast repeated inference
- invoked from `build_server()` after DB + similarity engine init
- [x] 3.3 Implement a “task embedding preload” routine executed on startup:
- fetch **all open tasks** (no limit)
- build `task_text = title + "\n\n" + description` (title optional)
- embed only cache-missing `task_id` vectors
- store into SQLite embedding cache
- [x] 3.4 Runtime fallback:
- if a role/competence/task embedding is missing during a tool call, embed on-demand (strict semantics)
- [x] 3.5 Tests:
- preload embeds only cache misses
- preload writes to SQLite
- missing-at-runtime triggers on-demand embedding
## Phase 4: Embedding-only inference (roles + competences)
- [x] 4.1 Implement primary role inference against role vocabulary:
- input: combined `title+description` (or description-only for ad-hoc)
- output: single closest role with similarity score as Markdown table: `Role | Similarity`
- [x] 4.2 Add a new MCP tool:
- `infer_primary_role(task_id? | task_text?)`
- returns: Markdown table `Role | Similarity` (single best matching role)
- validation: exactly one of `task_id` or `task_text` must be provided
- [x] 4.3 Remove deprecated tools and methods:
- remove `infer_roles(...)` MCP tool completely
- remove `TaskAnalyzer.extract_ranked_roles()` method completely
- [x] 4.4 Update callers to use new primary-role inference:
- update `get_task_details` to use `infer_primary_role`
- update `extract_requirements()` to use new primary-role inference
- update any internal helpers relying on `TaskAnalyzer.extract_ranked_roles()`
## Phase 5: Rework task validation (embedding-only)
- [x] 5.1 Replace chat-based extraction in `validate_task_requirements` / `extract_requirements_from_task` with embedding-based inference:
- roles: infer primary role via role vocabulary (Markdown table output)
- competences: infer competences via competence vocabulary with max X and threshold (Markdown table output)
- [x] 5.2 Update output format:
- keep current table-first display
- add inferred primary role as Markdown table: `Role | Similarity` (single row)
- add inferred competences as Markdown table: `Competence | Similarity` (descending order by similarity)
- [x] 5.3 Tests:
- validation uses embeddings path only (no chat calls)
- respects max X and threshold rules
- output includes role and competence similarity tables
## Phase 6: New MCP tools (capacity browsing + capacity→task matching)
- [x] 6.1 Implement `list_free_capacities(limit=20)`:
- ordering: `creation_date DESC`
- Markdown table columns: `capacity_id`, `Owner/Team`, `Role`, `Competences`, `Availability`
- stable empty-table handling (dummy row)
- [x] 6.2 Implement `get_capacity_details(capacity_id)`:
- table-first, same fields as list
- include a “Next steps” pointer to `find_matching_tasks(capacity_id=...)`
- [x] 6.3 Implement `find_matching_tasks(capacity_id)`:
- signature: takes only `capacity_id` (no optional params)
- fetch capacity + all open tasks
- infer each tasks primary role via embedding similarity (title+description)
- infer each tasks competences via embedding similarity (title+description) with max/threshold
- compute a similarity score (role + competences) and categorize results
- return output as Markdown table with columns:
- `task_id`, `Title`, `Required Competences`, `Availability`, `Score`, `Category`
- results are sorted by similarity score (descending)
- output structure matches `find_matching_capacities` (includes summary counts table + results table for first non-empty category)
- paginated via same search session conventions used by `find_matching_capacities`
- [x] 6.4 Tests for new tools:
- `list_free_capacities`: ordering by `creation_date DESC`, empty-table handling
- `get_capacity_details`: not-found handling, "Next steps" section
- `find_matching_tasks`: categorization (Top/Good/Partial/Low), search session conventions, table structure, score sorting
## Phase 7: Remove Azure chat API
- [x] 7.1 Remove chat config fields and environment variables if no longer needed (`AZURE_OPENAI_LLM_API_KEY`, chat deployment, chat codepaths).
- [x] 7.2 Delete/retire chat client methods and `TaskAnalyzer` chat-based methods.
- [x] 7.3 Update docs to remove chat setup instructions.
- [x] 7.4 Ensure all gates pass (ruff/mypy/pytest).
## Phase 8: Documentation + assistant prompt + architecture
- [x] 8.1 Update `README.md`:
- describe new tools and capacity→task workflow
- document startup embedding preload and inference config knobs
- [x] 8.2 Update `docs/assistant_system_prompt.md`:
- add guidance for new tools and matching direction
- update role/competence inference description (embeddings-only)
- [x] 8.3 Update architecture docs (Arc42 / design docs):
- reflect removal of chat API
- describe vocab/task embedding preload and cache keys
## Phase 9: OpenSpec hygiene
- [x] 9.1 Update spec deltas to reflect confirmed tool shapes + config.
- [x] 9.2 Run `openspec validate add-capacity-to-task-matching-tools --strict`.