|
| 1 | +# Agent Contract |
| 2 | + |
| 3 | +> How any agent consumes a resolved loadout. |
| 4 | +> Version: 1.4.0 |
| 5 | +
|
| 6 | +## Overview |
| 7 | + |
| 8 | +ai-loadout provides a portable knowledge routing contract. This document defines how agents — Claude Code, MCP servers, CLI wrappers, editor extensions, or any other consumer — integrate against it. |
| 9 | + |
| 10 | +The contract is three things: |
| 11 | + |
| 12 | +1. **A resolved-loadout schema** — the stable output shape agents receive |
| 13 | +2. **A match-and-load sequence** — the steps agents follow |
| 14 | +3. **Integration patterns** — how different agent types wire it up |
| 15 | + |
| 16 | +## The Sequence |
| 17 | + |
| 18 | +Every agent follows the same five steps: |
| 19 | + |
| 20 | +``` |
| 21 | +1. RESOLVE — discover and merge layered indexes |
| 22 | +2. MATCH — score entries against the current task |
| 23 | +3. DECIDE — separate preload / on-demand / manual |
| 24 | +4. LOAD — read payload files into context |
| 25 | +5. RECORD — log what was loaded (optional, enables observability) |
| 26 | +``` |
| 27 | + |
| 28 | +Steps 1-3 are handled by `planLoad(task)`. Steps 4-5 are the agent's responsibility. |
| 29 | + |
| 30 | +## API Surface |
| 31 | + |
| 32 | +### `planLoad(task, opts?)` |
| 33 | + |
| 34 | +The primary integration point. Returns a `LoadPlan`: |
| 35 | + |
| 36 | +```typescript |
| 37 | +import { planLoad } from "@mcptoolshop/ai-loadout"; |
| 38 | + |
| 39 | +const plan = planLoad("set up CI pipeline for the new repo"); |
| 40 | + |
| 41 | +// plan.preload — MatchResult[] — load these immediately (core entries) |
| 42 | +// plan.onDemand — MatchResult[] — load when task warrants it (domain entries) |
| 43 | +// plan.manual — LoadoutEntry[] — only on explicit request |
| 44 | +// plan.provenance — Record<string, string> — entryId → source layer |
| 45 | +// plan.budget — Budget — token budget summary |
| 46 | +// plan.conflicts — MergeConflict[] — entries overridden across layers |
| 47 | +// plan.layerNames — string[] — which layers contributed |
| 48 | +// plan.preloadTokens — number — total tokens in preload set |
| 49 | +// plan.onDemandTokens — number — total tokens in on-demand set |
| 50 | +``` |
| 51 | + |
| 52 | +### `recordLoad(entryId, trigger, mode, tokensEst, opts?)` |
| 53 | + |
| 54 | +Optional. Records that an entry was loaded into context. |
| 55 | + |
| 56 | +```typescript |
| 57 | +import { recordLoad } from "@mcptoolshop/ai-loadout"; |
| 58 | + |
| 59 | +recordLoad("github-actions", "keyword-ci", "lazy", 330, { |
| 60 | + usagePath: ".claude/loadout-usage.jsonl", |
| 61 | + taskHash: "abc123", |
| 62 | +}); |
| 63 | +``` |
| 64 | + |
| 65 | +### `manualLookup(id, opts?)` |
| 66 | + |
| 67 | +Explicit lookup for manual-priority entries. |
| 68 | + |
| 69 | +```typescript |
| 70 | +import { manualLookup } from "@mcptoolshop/ai-loadout"; |
| 71 | + |
| 72 | +const entry = manualLookup("xrpl-reference"); |
| 73 | +if (entry) { |
| 74 | + // read entry.path, load into context |
| 75 | +} |
| 76 | +``` |
| 77 | + |
| 78 | +## LoadPlan Schema |
| 79 | + |
| 80 | +The stable output shape agents integrate against: |
| 81 | + |
| 82 | +| Field | Type | Stability | Description | |
| 83 | +|-------|------|-----------|-------------| |
| 84 | +| `preload` | `MatchResult[]` | Stable | Core entries — always load these | |
| 85 | +| `onDemand` | `MatchResult[]` | Stable | Domain entries — load when task matches | |
| 86 | +| `manual` | `LoadoutEntry[]` | Stable | Manual entries — explicit lookup only | |
| 87 | +| `provenance` | `Record<string, string>` | Stable | entryId → source layer name | |
| 88 | +| `budget` | `Budget` | Stable | Token budget from resolved index | |
| 89 | +| `conflicts` | `MergeConflict[]` | Stable | Entries defined in multiple layers | |
| 90 | +| `layerNames` | `string[]` | Stable | Contributing layer names in order | |
| 91 | +| `preloadTokens` | `number` | Stable | Sum of preload entry tokens | |
| 92 | +| `onDemandTokens` | `number` | Stable | Sum of on-demand entry tokens | |
| 93 | + |
| 94 | +### MatchResult (per entry) |
| 95 | + |
| 96 | +| Field | Type | Description | |
| 97 | +|-------|------|-------------| |
| 98 | +| `entry` | `LoadoutEntry` | The full entry with id, path, keywords, etc. | |
| 99 | +| `score` | `number` | 0-1, match strength | |
| 100 | +| `matchedKeywords` | `string[]` | Which keywords matched | |
| 101 | +| `matchedPatterns` | `string[]` | Which patterns matched | |
| 102 | +| `reason` | `string` | Human-readable explanation | |
| 103 | +| `mode` | `LoadMode` | `"eager"` / `"lazy"` / `"manual"` | |
| 104 | + |
| 105 | +## Layer Resolution |
| 106 | + |
| 107 | +The resolver checks fixed locations in a fixed order: |
| 108 | + |
| 109 | +| Priority | Layer | Location | |
| 110 | +|----------|-------|----------| |
| 111 | +| 1 (lowest) | `global` | `~/.ai-loadout/index.json` | |
| 112 | +| 2 | `org` | `$AI_LOADOUT_ORG` or explicit path | |
| 113 | +| 3 | `project` | `<cwd>/.claude/loadout/index.json` | |
| 114 | +| 4 (highest) | `session` | `$AI_LOADOUT_SESSION` or explicit path | |
| 115 | + |
| 116 | +Later layers override earlier ones for the same entry ID. Missing layers are normal. |
| 117 | + |
| 118 | +## Integration Patterns |
| 119 | + |
| 120 | +### Claude Code Agent |
| 121 | + |
| 122 | +The most common pattern. CLAUDE.md references the loadout, and the agent uses keyword matching to load rules on demand. |
| 123 | + |
| 124 | +``` |
| 125 | +.claude/ |
| 126 | + loadout/ |
| 127 | + index.json ← dispatch table |
| 128 | + rules/ |
| 129 | + github-actions.md ← payload files |
| 130 | + shipcheck.md |
| 131 | + ... |
| 132 | +CLAUDE.md ← references loadout, instructs lazy loading |
| 133 | +``` |
| 134 | + |
| 135 | +The agent: |
| 136 | +1. Reads CLAUDE.md (which includes the dispatch table or a pointer to it) |
| 137 | +2. On each task, matches against the index |
| 138 | +3. Loads matching payloads via the Read tool |
| 139 | +4. Records loads to `.claude/loadout-usage.jsonl` |
| 140 | + |
| 141 | +### MCP Server |
| 142 | + |
| 143 | +An MCP server can expose loadout matching as a tool: |
| 144 | + |
| 145 | +``` |
| 146 | +Tool: match_knowledge |
| 147 | +Input: { task: "deploy to production" } |
| 148 | +Output: { entries: [...], budget: {...} } |
| 149 | +``` |
| 150 | + |
| 151 | +The server calls `planLoad()` internally and returns the plan. The calling agent decides what to load. |
| 152 | + |
| 153 | +### CLI Wrapper |
| 154 | + |
| 155 | +A CLI tool wraps the runtime for shell-based workflows: |
| 156 | + |
| 157 | +```bash |
| 158 | +# What should I load for this task? |
| 159 | +ai-loadout resolve |
| 160 | + |
| 161 | +# Why did this entry win? |
| 162 | +ai-loadout explain github-actions |
| 163 | + |
| 164 | +# After a session, what went unused? |
| 165 | +ai-loadout dead .claude/loadout/index.json usage.jsonl |
| 166 | +``` |
| 167 | + |
| 168 | +### Editor Extension |
| 169 | + |
| 170 | +An editor extension (VS Code, etc.) can use the runtime to suggest relevant knowledge files when the user opens a project or starts a task. |
| 171 | + |
| 172 | +## Observability Contract |
| 173 | + |
| 174 | +Usage recording is optional but enables three diagnostic capabilities: |
| 175 | + |
| 176 | +| Capability | Function | Requires | |
| 177 | +|-----------|----------|----------| |
| 178 | +| Dead entry detection | `findDeadEntries()` | Usage log | |
| 179 | +| Budget drift analysis | `analyzeBudget()` | Usage log | |
| 180 | +| Frequency tracking | `summarizeUsage()` | Usage log | |
| 181 | + |
| 182 | +Usage events are: |
| 183 | +- **Append-only** — never modified or deleted |
| 184 | +- **Local-only** — never transmitted over the network |
| 185 | +- **JSONL format** — one JSON object per line |
| 186 | +- **Optional** — the system works without recording |
| 187 | + |
| 188 | +## Guarantees |
| 189 | + |
| 190 | +1. **Deterministic** — same inputs produce the same plan (except timestamps) |
| 191 | +2. **Graceful degradation** — missing layers, files, or configs don't crash |
| 192 | +3. **No network** — everything is local filesystem |
| 193 | +4. **No side effects** — `planLoad()` only reads; `recordLoad()` only appends |
| 194 | +5. **Backward compatible** — new fields are additive; existing fields don't change meaning |
| 195 | +6. **Zero dependencies** — no runtime deps beyond Node.js |
0 commit comments