Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion agents/roadmaps/road-to-v1-0.md
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ complexity: heavy
> **Goal.** Plugin can connect to arbitrary MCP servers (per `.agent-settings.yml::mcp.servers`). All 136 agent-config commands are callable via slash-picker. Memories work (lokal + optional `@event4u/agent-memory` MCP backend). Hooks (sessionStart/End/Stop) respect agent-config hook system.

- [x] **T-1101 — MCP Client.** <!-- done 2026-05-30: packages/core/src/mcp/ — protocol.ts (zod JSON-RPC + MCP tool/result schemas, contentToText), transport.ts (McpTransport seam + StdioTransport spawning NDJSON over stdio, reusing llm/ndjson.ts), fake-transport.ts (deterministic in-memory test transport), client.ts (McpClient: initialize handshake + notifications/initialized, listTools, callTool, id-correlated requests, bounded init/request timeouts, fail-open markDead rejecting in-flight on transport death), registry.ts (McpToolRegistry: aggregate clients keyed by server-id, prefix `<server-id>:<tool>`, route callTool, map to ToolDefinition), manager.ts (McpManager: reads mcp.servers[], spawns+connects each fail-open, per-server degrade, dispose). agent-settings.ts gained mcp.servers schema (id rejects ':'). HAND-ROLLED, no @modelcontextprotocol/sdk → ADR-006 (no-native-deps law + ADR-003 NDJSON precedent; council codex/gpt-5.5+gemini-2.5-pro split, gemini direct). 25 mcp tests + 3 settings tests, full core suite 506 pass, task ci green. --> Hand-rolled minimal MCP stdio client (ADR-006, not `@modelcontextprotocol/sdk`). Reads `.agent-settings.yml::mcp.servers[]`, spawns each as subprocess, manages JSON-RPC connection. Tool registry includes MCP tools with prefix `<server-id>:<tool-name>`.
- [x] **T-1102 — agent-config MCP server consumption.** <!-- done 2026-05-30: packages/core/src/mcp/agent-config-client.ts — AgentConfigMcpClient typed convenience over McpClient (memoryLookup, chatHistoryRead, listSkills, skillRead, commandRead → flattened {text,isError}); DEFAULT_AGENT_CONFIG_SERVER = {id:'agent-config', command:'npx', args:['@event4u/agent-config','mcp']}. 3 tests with FakeTransport echo responder. The live connection at runtime is wired into the dispatcher in the IDE-runtime exit gate. --> Connect to the agent-config-shipped MCP server (`npx @event4u/agent-config mcp`). Use its tools (`memory_lookup`, `chat_history_read`, `list_skills`, `skill_read`, `command_read`) instead of our own filesystem readers for skill/rule/command content.
- [x] **T-1102 — agent-config MCP server consumption.** <!-- done 2026-05-30: packages/core/src/mcp/agent-config-client.ts — AgentConfigMcpClient typed convenience over McpClient (memoryLookup, chatHistoryRead, listSkills, skillRead, commandRead → flattened {text,isError}); DEFAULT_AGENT_CONFIG_SERVER = {id:'agent-config', command:'npx', args:['@event4u/agent-config','mcp']}. 3 tests with FakeTransport echo responder. The live connection at runtime is wired into the dispatcher in the IDE-runtime exit gate. LOCAL-WALKER SIBLING 2026-06-02 (ADR-050): the MCP `list_skills`/`skill_read` here is IDE-gated (MCP server lifecycle is native); the offline local-walker authority path now ships as the `configList {kind?,limit?}` protocol method on a new `ConfigHandler`, wiring the previously-dead `agent-config-walker.ts::indexByKind` to surface skills + rules (+ commands) the command palette (ADR-048) does not — `ConfigSummary {kind,name,description,path}`, `MAX_CONFIG_LIST_RESULTS=100` + total, +3 DTOs (64→67), `task ci` exit 0, `jetbrains:check` green. Mirrors `commandList` ↔ MCP `command_read`. The skill picker / rules viewer render stays IDE. --> Connect to the agent-config-shipped MCP server (`npx @event4u/agent-config mcp`). Use its tools (`memory_lookup`, `chat_history_read`, `list_skills`, `skill_read`, `command_read`) instead of our own filesystem readers for skill/rule/command content.
- [~] **T-1103 — All 136 commands callable.** <!-- core done 2026-05-30: packages/core/src/commands/loader.ts loadCommandProcedure(name, {mcp?, localNodes}) — prefers agent-config MCP command_read, falls back to local walker body, reports missing; 5 tests. [~] not [x]: the slash-picker overlay rendering all commands + the actual invocation UX are webview/IDE-runtime surfaces (the picker filter pure-fn already exists from T-402); they bind to this loader but need a GUI session to verify. TRANSPORT WIRED 2026-06-02 (ADR-048, PR pending): the dead picker (`commandsToPickerItems`/`pickCommands`, T-402) + loader (`loadCommandProcedure`) are now exposed over two read-only protocol methods on a new `CommandHandler` (walk-once-cached `walkAgentConfig(cwd)`, the live collaborator from ADR-043): `commandList {query?}` (empty → all alphabetical, query → ranked; `total` = match count before a `MAX_COMMAND_LIST_RESULTS=100` cap — council Q1=B/Q2=A) + `commandRead {name}` (MCP-first/local-fallback body — Q1=B; core stays the resolution authority so the agent + palette agree and it works offline — Q5/Q6). `CommandSummary {name,description,path}` reuses the PickerItem shape (Q3=A); absolute path kept for IDE click-through (Q4=A); `requireCommands()` → `commands_not_configured`; fail-open walk. +5 DTOs (59→64), `jetbrains:check` BUILD SUCCESSFUL, +15 core tests incl. a buildCoreDispatcher live-wiring proof. STILL `[~]` — the overlay rendering, favourites, and invocation UX are IDE surfaces; an agent-config MCP client (so `commandRead` prefers it) is a documented follow-up. --> Slash-picker shows all commands from the agent-config tree (filtered by user-set favourites if Phase 0 Spike 0.5 recommended favourites). Each command's procedure is loaded via the MCP `command_read` tool at invocation time.
- [x] **T-1104 — Memories — local.** <!-- done 2026-05-30: packages/core/src/memory/local.ts — LocalMemoryStore over `.event4u-agent/memories/` in Markdown+YAML-frontmatter (name/description/metadata.type) + regenerated MEMORY.md index, NOT JSON: the roadmap's "JSON" wording yields to the real agent-config on-disk contract per ADR-006 (council unanimous). list/read/write/delete/regenerateIndex + serializeRecord; reads agent-config-authored files for external compat; kebab-name validation. 8 tests. --> `packages/core/src/memory/local.ts` reads/writes `.event4u-agent/memories/` in agent-config's md+frontmatter+MEMORY.md format (ADR-006, not JSON). Two memory types in MVP: `user` (long-lived) and `feedback` (workflow corrections).
- [x] **T-1105 — Memories — MCP backend.** <!-- done 2026-05-30: packages/core/src/memory/backend.ts — MemoryBackend iface; LocalMemoryBackend (filterRecords over the store), McpMemoryBackend (memory_lookup/memory_write via McpClient + best-effort parseMcpRecords), RoutingMemoryBackend (MCP-first lookup with local fallback on throw; writes always mirror locally so a later outage still serves). 9 tests. --> Optional: if `@event4u/agent-memory` MCP server configured, route memory_lookup/memory_write calls there. Local fallback if MCP server unreachable.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,29 @@ data class CommandReadResponse(
val body: String,
)

/** An agent-config artifact's metadata (kind=skill/rule/command), for the IDE registry/picker. */
@Serializable
data class ConfigSummary(
val kind: String,
val name: String,
val description: String,
val path: String,
)

/** List agent-config artifacts; absent kind lists all (skills, rules, commands). limit clamped by Core. */
@Serializable
data class ConfigListRequest(
val kind: String? = null,
val limit: Int? = null,
)

/** Artifact summaries grouped by kind then name; total is the match count before the cap. */
@Serializable
data class ConfigListResponse(
val items: List<ConfigSummary>,
val total: Int,
)

/** Start an agentic chat turn (LLM + tool loop). maxIterations caps the loop; omitted = Core default. */
@Serializable
data class AgentTurnRequest(
Expand Down
117 changes: 117 additions & 0 deletions docs/adr/ADR-050-config-list-skills-rules-data-path.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
---
adr: 050
title: Agent-Config Registry Data Path — Wiring The Dead indexByKind As A configList Protocol Method For Skills + Rules (T-401)
status: Proposed (drafted 2026-06-02 — awaits user sign-off before flip to Accepted)
deciders: solo-dev (event4u team lead) — sign-off required
consulted: AI Council — gemini-cli 0.41.2 answered Q0–Q7 = A/B/A/B/A/A/A/A (2026-06-02); codex-cli 0.134.0 was invoked with the same question set but hung >7 min producing zero output and was terminated (a recurring codex-exec flakiness this repo has hit before), so the second opinion is gemini + the host agent's own seam-hunt + independent caller verification. The one reasoned divergence from gemini is Q6 (see Decision).
related: sibling of ADR-048 (command-palette data path) for the artifact kinds it does not surface — skills + rules. The MCP path (`AgentConfigMcpClient.listSkills`/`skillRead`, T-1102) is IDE-gated (MCP server lifecycle is IDE-native); this is the local-walker authority/offline sibling, mirroring how `commandList` relates to the MCP `command_read`. Reuses the `walkAgentConfig` collaborator already live for the rules loader (ADR-043) and the command palette (ADR-048).
date: 2026-06-02
---

# ADR-050 — Agent-Config Registry Data Path (configList, T-401)

## Status

**Proposed** — awaits sign-off. One branch / one PR, committed in logical
chunks (protocol+codegen → core handler+wiring → tests → docs), preserving
minimal-safe-diff.

CI-verified locally: `task ci` exit 0 (lint, format, build, typecheck, test —
protocol 55, shared 5, vscode 40, core 1132 pass / 1 skip). `task
jetbrains:check` BUILD SUCCESSFUL (the +3 Kotlin DTOs compile). **No checkbox
flip** — the skill picker / rules viewer render remains an IDE surface; this is
the headless data path those surfaces will call.

## Context

The pure-core "wire a dead seam" runway after ADR-049 looked thin, but a fresh
Explore seam-hunt + independent caller verification found one genuinely-dead,
non-artificial core export:

`packages/core/src/config/agent-config-walker.ts::indexByKind` groups a flat
`ConfigNode[]` into `{skill[], rule[], command[]}`. It is fully unit-tested and
has **zero live callers** (only its own test imports it — verified by a
whole-repo grep excluding `*.test.ts`/`dist`). The Explore agent's other
candidates were rejected on cross-check: `phaseRunsInMode` (IDE-gated,
already a recorded reject), `sortAndDedup` (live at `review/pipeline.ts:308`),
`dismissalFor` (live inside `review/dismissals.ts`).

The capability gap is real, not invented. `walkAgentConfig` discovers **all**
agent-config artifacts — skills, rules, and commands — but the only protocol
data path that ships (ADR-048's `CommandHandler`) filters the walked tree to
`kind === 'command'`. Skills and rules — also walked, also in the index — have
**no** protocol data path. The IDE cannot populate a skill picker or a rules
viewer. `indexByKind` is exactly the grouping helper that path needs.

## Decision

Wire `indexByKind` over one new read-only protocol method, `configList
{kind?, limit?}` → `{items: ConfigSummary[], total}`, on a **dedicated
`ConfigHandler`**. `ConfigSummary {kind, name, description, path}` mirrors
`CommandSummary` and adds `kind` so one flat list can carry skills, rules, and
commands. Absent `kind` → every artifact (skills, then rules, then commands,
alphabetical within each kind as the walker sorts); a `kind` → that kind only.
`total` is the count before a `MAX_CONFIG_LIST_RESULTS=100` clamp so a browser
can show "showing N of M". Description falls back frontmatter `description` →
first heading → `''`, and a non-empty frontmatter `name` overrides the slug —
both identical to the command picker's projection.

Council resolution (gemini Q0–Q7 = A/B/A/B/A/A/A/A):

- **Q0=A** — wire it (the dead seam fills a real gap).
- **Q1=B** — return all three kinds; the unified registry is one call, and
commands appearing here (without the slash-palette fuzzy ranking) is
harmless. The kind filter narrows to skills or rules.
- **Q2=A** — optional `kind` filter; absent returns all.
- **Q3=B** — flat `{items, total}` with a `kind` field per item, mirroring
`commandList` rather than a grouped object.
- **Q4=A** — `{kind, name, description, path}`; absolute path for IDE
click-through, no `relPath`/`sourceRoot` bloat (YAGNI).
- **Q5=A** — lightweight summary only; no frontmatter map or body on the wire
(a body-read method can follow if a surface needs it).
- **Q6 — reasoned divergence.** Gemini chose A (extend `CommandHandler`,
reusing its cached walk). The host agent chose **B (a dedicated
`ConfigHandler`)** for two reasons: (1) it preserves the codebase's
one-handler-per-domain shape (`ChatHandler`, `GitHandler`, `CommandHandler`,
`CostReporter`, `TerminalHandler`) — overloading the command handler with
skill/rule listing muddies its contract; (2) **correctness** — gemini's
cache-reuse benefit would require a sidecar-level shared walk promise, which
would cache a *transient* FS error for the whole session, defeating each
handler's fail-open-and-retry cache. Each handler keeps its own
`loadNodes: () => walkAgentConfig(cwd)`. The cost is one extra tree walk on
first use; config trees are small (the same premise behind Q7), so this is
negligible and buys the fail-open resilience.
- **Q7=A** — hard `MAX_CONFIG_LIST_RESULTS=100` clamp + carry `total`,
sibling-consistent with `commandList` / `conversationList`.

## Consequences

- **+3 DTOs** (`ConfigSummary`, `ConfigListRequest`, `ConfigListResponse`),
64 → 67 in codegen; +1 protocol method (`configList`), behind
`requireConfig()` → coded `config_not_configured` when absent.
- The skill picker / rules viewer now have a headless, offline-capable data
path that does not depend on the agent-config MCP server being up — Core
stays the resolution authority, exactly as for `commandList`.
- No behaviour change to any existing path; `CommandHandler` is untouched.
- The IDE render (skill picker overlay, rules viewer) remains the last-mile
surface; this ADR does not flip any roadmap checkbox.

## Alternatives considered

- **Leave `indexByKind` dead (Q0=B).** Rejected — it fills a real gap and the
wiring is a thin, low-risk pure-core slice.
- **Extend `CommandHandler` (Q6=A).** Rejected for the contract-clarity +
fail-open-resilience reasons above.
- **Skills only (Q1=C).** Rejected — rules also lack a UI data path; a unified
registry with a kind filter covers both at no extra cost.
- **Include frontmatter/body in the summary (Q5=B/C).** Deferred (YAGNI) — a
separate read method can fetch a body when a surface needs it.

## References

- `packages/core/src/config/handler.ts` — the new `ConfigHandler`.
- `packages/core/src/config/agent-config-walker.ts::indexByKind` — the wired seam.
- ADR-048 — the command-palette sibling this mirrors.
- ADR-043 — the `walkAgentConfig` live collaborator reused here.
- `agents/roadmaps/road-to-v1-0.md` T-401 — the walker; T-1102 — the MCP
skill/rule path this complements with a local-walker authority path.
1 change: 1 addition & 0 deletions docs/adr/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
| [ADR-047](ADR-047-embedding-cache-persistence.md) | Embedding-Cache Persistence — Wiring The EmbeddingCache `store?` Seam To Disk So A Cold Start Skips Re-Embedding (T-805) | Proposed | 2026-06-02 | road-to-v1-0 T-805/T-806 — the sidecar re-embeds the whole repo on every startup; the shipped `EmbeddingCache` was in-memory (its `store?` ctor seam was never seeded from disk) and `VectorStore.toBuffer`/`fromBuffer` were dead. Persists the content-hash cache (not the position-addressed vector store) so the startup re-walk hits it instead of re-embedding. Adds `EmbeddingCacheStore` (packed-Float32 Buffer, header guard `{cacheVersion, modelId, dimensions}`, atomic temp+rename), `cacheStore?` on ContextEngine + `embeddingCacheDir?` on WorkspaceCoordinator (default-off), load-before / fail-soft-persist-after the index walk; sidecar passes `<state>/embeddings` only with a real embedder. Council UNANIMOUS Q0=A (cache over vector store), Q1=A (header guard), Q2=A (default-off threading), Q3=A (load-once, coalesced atomic write, fail-soft), Q5=A; Q4 traps — atomic write (codex) + persist only the working set touched this session (gemini, bounds growth). +16 tests; no protocol/codegen change; no checkbox flip (T-805 already `[x]`, this makes it true across restarts) |
| [ADR-048](ADR-048-command-palette-data-path.md) | Command-Palette Data Path — Wiring The Dead Picker + Loader As commandList / commandRead Protocol Methods (T-402 / T-1103) | Proposed | 2026-06-02 | road-to-v1-0 T-402/T-1103 — the shipped-but-dead `commands/picker.ts` (`commandsToPickerItems`/`pickCommands`) + `commands/loader.ts` (`loadCommandProcedure`) had zero live callers; the live collaborator `walkAgentConfig` is already walked (rules loader, ADR-043). Adds two read-only methods on a new `CommandHandler` over the walk-once-cached command index: `commandList {query?}` (empty → all alphabetical, query → ranked; `total` = match count before a `MAX_COMMAND_LIST_RESULTS=100` cap) + `commandRead {name}` (MCP-first/local-fallback body), behind `requireCommands()`. Council UNANIMOUS Q0=A (wire the data path), Q1=B (both methods), Q2=A (one `commandList{query?}`), Q3=A (`CommandSummary {name,description,path}` = PickerItem), Q4=A (keep absolute path for IDE click-through), Q6=A; Q5 born-dead risk → core stays the resolution authority. +5 DTOs (59→64); `jetbrains:check` BUILD SUCCESSFUL; no checkbox flip (the overlay render + invocation UX stay IDE) |
| [ADR-049](ADR-049-retire-dead-context-inject.md) | Retire The Dead Context-Injection Twin — context/inject.ts Superseded By The Live buildContextInjection (T-605 / T-606) | Proposed | 2026-06-02 | road-to-v1-0 T-605/T-606 — `context/inject.ts` (`buildContextBlock`/`injectContext`, user-message placement) was the recorded T-605/T-606 impl but had zero live callers; the live path is `chat/context-injection.ts::buildContextInjection` (T-MR13, ADR-025, system-prompt placement) used in both handlers. Retires the dead twin + its test. Council UNANIMOUS Q0=A (retire), Q1=A (dedicated ADR mirroring ADR-039), Q2=A (T-605/T-606 stay `[x]`, done-comments updated to cite the live impl), Q3=A (nothing to port — the fixed 8000-char cap supersedes the dynamic 20%-window budget), Q4 no trap. No protocol/codegen change; no checkbox flip (capability is live via the system-prompt placement) |
| [ADR-050](ADR-050-config-list-skills-rules-data-path.md) | Agent-Config Registry Data Path — Wiring The Dead indexByKind As A configList Protocol Method For Skills + Rules (T-401) | Proposed | 2026-06-02 | road-to-v1-0 T-401 — `agent-config-walker.ts::indexByKind` (groups walked `ConfigNode[]` into `{skill,rule,command}`) was unit-tested with zero live callers; the only shipped data path (ADR-048 `CommandHandler`) filters to `kind==='command'`, so skills + rules — also walked — had no protocol path for a skill picker / rules viewer. Adds read-only `configList {kind?, limit?}` → `{items: ConfigSummary[], total}` (`ConfigSummary {kind,name,description,path}`) on a dedicated `ConfigHandler` behind `requireConfig()`, the local-walker authority/offline sibling of the MCP `listSkills` (T-1102). Council (gemini-cli; codex-cli hung >7 min, terminated) Q0=A (wire it), Q1=B (all kinds, kind-filterable), Q2=A (optional `kind`), Q3=B (flat `{items,total}` + per-item kind), Q4=A (path for click-through), Q5=A (lightweight only), Q7=A (`MAX_CONFIG_LIST_RESULTS=100` + total); Q6 reasoned divergence — dedicated handler over extending `CommandHandler` (one-handler-per-domain + per-handler fail-open walk cache vs a shared promise caching a transient FS error). +3 DTOs (64→67); `task ci` exit 0; `jetbrains:check` BUILD SUCCESSFUL; no checkbox flip (the picker/viewer render stays IDE) |

## Status legend

Expand Down
Loading
Loading