algocline 0.47.0

LLM amplification engine — MCP server with Lua scripting
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
{
  "crate": "algocline-engine",
  "version": "0.45.0",
  "items": [
    {
      "path": "algocline_engine::bridge",
      "kind": "module",
      "docs": "Layer 0: Runtime Primitives\n\nRegisters Rust-backed functions into the `alc.*` Lua namespace.\nThese provide capabilities that cannot be expressed in Pure Lua:\nI/O (state), serialization (json), host communication (llm),\nand text processing (chunk).\n\nAll functions registered here are available in every Lua session\nwithout explicit `require()`."
    },
    {
      "path": "algocline_engine::bridge::BridgeConfig",
      "kind": "struct",
      "docs": "All handles needed by Layer 0 runtime primitives.\n\nCollects the various per-session handles into a single config,\navoiding a growing parameter list on `register()`."
    },
    {
      "path": "algocline_engine::bridge::PRELUDE",
      "kind": "constant",
      "docs": "Layer 1 prelude (also used by fork to setup child VMs)."
    },
    {
      "path": "algocline_engine::bridge::install_for_pkg_test",
      "kind": "function",
      "docs": "Install the production `alc.*` primitive surface plus the mock layer\non `lua` for use by the `alc_pkg_test` sandbox.\n\nSpec authors get:\n* the full `alc.*` surface that `alc_run` exposes (stateless helpers\n  like `alc.json_encode`, `alc.fingerprint`, `alc.parse_number`,\n  `alc.fuzzy.*`, plus stateful helpers backed by in-memory\n  per-VM tempdirs for `alc.state.*` and `alc.card.*`);\n* `alc.llm` / `alc.llm_batch` / `alc.fork` as stubs that error out\n  when called without a `with_alc({ llm = … }, …)` override;\n* a Pure-Lua mock layer (`with_alc(overrides, fn)`,\n  `alc_mock.install/restore`, `alc.spy(name, default_fn?)`).\n\n**Invariant** (enforced by `tests/bridge_sandbox_parity.rs`):\n`production primitive surface ⊆ test sandbox primitive surface`.\nEvery key reachable on `_G.alc` after a successful production\n[`register`] call is also reachable after `install_for_pkg_test`.\n\nThe per-VM tempdir backing `state_store` / `card_store` is held on\nthe Lua VM via `set_app_data` and dropped together with the VM."
    },
    {
      "path": "algocline_engine::bridge::register",
      "kind": "function",
      "docs": "Register all Layer 0 runtime primitives onto the given table."
    },
    {
      "path": "algocline_engine::card",
      "kind": "module",
      "docs": "Card storage — immutable run-result snapshots.\n\nA Card is a frozen record of a strategy run: identity, parameters,\nmodel, scenario, aggregate stats, and (optionally) per-case detail.\nCards are **immutable** — once written they are never modified, only\nannotated via additive `append`.  Mutable **aliases** point to a\nCard and can be rebound freely.\n\n## Design principles\n\n1. **Minimal REQUIRED, maximal OPTIONAL** — v0 needs only 4 fields;\n   lightweight \"ran this pkg\" records and heavy optimize snapshots\n   share the same schema.\n2. **Immutable append-only** — no overwrite, no delete.  New data is\n   added via `append` (new top-level keys only) or by creating a new\n   Card with a fresh `card_id`.\n3. **Two-tier storage** — TOML for human-readable aggregate, JSONL\n   sidecar for machine-parseable per-case detail.\n4. **File-primary** — files are the source of truth; in-memory state\n   is cache.  Cards can be copied, diffed, and version-controlled.\n\n## Storage layout (two-tier)\n\n| Tier | File | Content |\n|------|------|---------|\n| **Tier 1** | `~/.algocline/cards/{pkg}/{card_id}.toml` | Aggregate scalars, decisions, identity, params |\n| **Tier 2** | `~/.algocline/cards/{pkg}/{card_id}.samples.jsonl` | Per-case raw data (JSONL, write-once) |\n\nTier 1 holds a shareable summary (a few KB). Tier 2 holds per-case\ndetail ��� the engine does not interpret its columns; packages define\ntheir own schema.\n\nAlias table: `~/.algocline/cards/_aliases.toml` (global).\n\n## card_id naming\n\n`{pkg}_{model_short}_{compact_ts}_{hash6}`\n\n- `compact_ts`: `YYYYMMDDTHHMMSS` in UTC\n- `hash6`: first 6 hex chars of DJB2 param fingerprint\n- Example: `cot_opus46_20260412T061500_a3f9c1`\n\n## v0 schema (frozen)\n\n### REQUIRED (minimum valid Card)\n\n| Field | Type | Example |\n|-------|------|---------|\n| `schema_version` | string | `\"card/v0\"` |\n| `card_id` | string | `\"cot_opus46_20260412T061500_a3f9c1\"` |\n| `created_at` | string (RFC 3339) | `\"2026-04-12T06:15:00Z\"` |\n| `[pkg].name` | string | `\"cot\"` |\n\n### OPTIONAL (auto-injected where possible)\n\n| Section | Fields |\n|---------|--------|\n| `[pkg]` | `version`, `category`, `source`, `source_ref`, `source_sha` |\n| `[runtime]` | `alc_version`, `lua_version`, `host_os`, `git_sha` |\n| `[model]` | `provider`, `id`, `id_short`, `cutoff` |\n| `[params]` | Free-form ctx snapshot; `param_fingerprint` for DJB2 hash |\n| `[strategy_params]` | Strategy-tunable parameters surfaced for sweeps / optimizers (e.g. `alpha`, `temperature`, `depth`). Free-form, but `where`-queryable as a first-class section |\n| `[scenario]` | `name`, `source`, `case_count`, `grader` |\n| `[stats]` | `pass_rate`, `mean_score`, `std`, `median`, `min`, `max`, `n` |\n| `[stats.by_bucket]` | Disaggregated sub-bucket stats (array of tables) |\n| `[cost]` | `llm_calls`, `input_tokens`, `output_tokens`, `elapsed_ms`, `usd_estimate` |\n| `[optimize]` | `target`, `search`, `rounds_used`, `top_k` (for optimize Cards) |\n| `[metadata]` | Free-form escape hatch. Recognized lineage conventions: `prior_card_id` (parent Card id), `prior_relation` (relation kind, e.g. `\"sweep_variant\"`, `\"reflection_of\"`, `\"derived_from\"`) |\n\n## Lua API (`alc.card.*`)\n\n| Function | Description |\n|----------|-------------|\n| `create(table)` | Write new Card (Tier 1). Returns `{ card_id, path }` |\n| `get(card_id)` | Read Card by id. Returns table or nil |\n| `list(filter?)` | List Cards as summaries (newest first) |\n| `find(query?)` | Prisma-style `where` DSL + dotted-path `order_by` + `offset`/`limit` |\n| `append(card_id, fields)` | Additive-only annotation (new keys only) |\n| `alias_set(name, card_id, opts?)` | Pin mutable alias |\n| `alias_list(filter?)` | List aliases |\n| `get_by_alias(name)` | Resolve alias → full Card |\n| `write_samples(card_id, samples)` | Write Tier 2 sidecar (write-once) |\n| `read_samples(card_id, opts?)` | Read Tier 2 with `where` filtering + offset/limit paging |\n| `lineage(query)` | Walk ancestry/descendants via `metadata.prior_card_id` |"
    },
    {
      "path": "algocline_engine::card::Alias",
      "kind": "struct"
    },
    {
      "path": "algocline_engine::card::CardEvent",
      "kind": "enum",
      "docs": "A Card-level event emitted from the write path.\n\nEach variant carries the already-serialized payload text so that\nsubscribers can persist the exact bytes that were written to the\nprimary store (byte-for-byte parity)."
    },
    {
      "path": "algocline_engine::card::CardEventBus",
      "kind": "struct",
      "docs": "Process-wide fan-out bus. Subscribers are registered once at startup\n(from `ALC_CARD_SINKS`) and stored behind a `Mutex` so that tests\ncan swap them out via `replace_subscribers_for_test` without losing\nthe singleton identity."
    },
    {
      "path": "algocline_engine::card::CardEventKind",
      "kind": "enum",
      "docs": "Lightweight discriminant for `CardEvent`. Used as a `HashMap` key in\n`SubscriberStats` so that ok/err counters can be tracked per event\nkind without holding the full payload."
    },
    {
      "path": "algocline_engine::card::CardStore",
      "kind": "trait",
      "docs": "Storage backend for Cards.\n\nImplementations must be `Send + Sync` so that they can be shared\nacross Lua host threads safely. All methods may fail with an\nerror `String` describing the backend-specific failure."
    },
    {
      "path": "algocline_engine::card::CardSubscriber",
      "kind": "trait",
      "docs": "A downstream backend that receives `CardEvent`s in best-effort,\nserial fan-out order.\n\nImplementations must be `Send + Sync` so that the bus can hold\n`Arc<dyn CardSubscriber>` safely across threads."
    },
    {
      "path": "algocline_engine::card::CmpOp",
      "kind": "enum",
      "docs": "Single comparison operator."
    },
    {
      "path": "algocline_engine::card::Comparison",
      "kind": "struct",
      "docs": "One parsed comparison: `path` points at a nested field,\n`op` + `value` describe how to compare it."
    },
    {
      "path": "algocline_engine::card::FileCardStore",
      "kind": "struct",
      "docs": "File-backed implementation of [`CardStore`]."
    },
    {
      "path": "algocline_engine::card::FileCardSubscriber",
      "kind": "struct",
      "docs": "A subscriber that mirrors events to a local directory using the\nsame two-tier layout as [`FileCardStore`]:\n\n- `{root}/{pkg}/{card_id}.toml` — Card TOML\n- `{root}/{pkg}/{card_id}.samples.jsonl` — samples sidecar\n- `{root}/_aliases.toml` — global alias table"
    },
    {
      "path": "algocline_engine::card::FindQuery",
      "kind": "struct",
      "docs": "Query parameters for `find`."
    },
    {
      "path": "algocline_engine::card::LastError",
      "kind": "struct",
      "docs": "Most recent delivery failure for a single subscriber. Exposed via\n`SubscriberHealthRow.last_error` in the `alc_stats` JSON snapshot."
    },
    {
      "path": "algocline_engine::card::LineageDirection",
      "kind": "enum",
      "docs": "Walk direction for `lineage`."
    },
    {
      "path": "algocline_engine::card::LineageEdge",
      "kind": "struct",
      "docs": "One edge in the lineage result (child → parent, always)."
    },
    {
      "path": "algocline_engine::card::LineageNode",
      "kind": "struct",
      "docs": "One node in the lineage result.\n\n`depth` is the signed distance from the root: negative for\nancestors (up-walk), 0 for the root, positive for descendants."
    },
    {
      "path": "algocline_engine::card::LineageQuery",
      "kind": "struct",
      "docs": "Query parameters for `lineage`."
    },
    {
      "path": "algocline_engine::card::LineageResult",
      "kind": "struct",
      "docs": "Full lineage walk result."
    },
    {
      "path": "algocline_engine::card::LogSinkCardSubscriber",
      "kind": "struct",
      "docs": "Fan-out `CardEvent`s into per-session `LogSink` ring buffers.\n\nProcess-wide singleton accessed via [`log_sink_subscriber`]. Each Session\nregisters its own `LogSink` on start-up via [`register_log_sink`] and\nunregisters on `Drop` of the returned [`LogSinkRegistration`] guard.\n\n# Concurrency\n\n`Send + Sync`. Internal state is `Mutex<Vec<(u64, LogSink)>>` +\n`AtomicU64`. `on_event` uses the same snapshot-clone-then-iterate\npattern as [`CardEventBus::publish`]: sinks are cloned under the lock\nthen iterated with the lock released, so registrations/unregistrations\nfrom other threads never deadlock a fan-out in flight. The mutex uses\nthe crate-wide `unwrap_or_else(|p| p.into_inner())` policy so a\npoisoned lock never propagates a panic."
    },
    {
      "path": "algocline_engine::card::LogSinkRegistration",
      "kind": "struct",
      "docs": "RAII guard that unregisters a `LogSink` from the singleton\n[`LogSinkCardSubscriber`] when dropped.\n\nReturned by [`register_log_sink`]. The guard owns a strong `Arc` to\nthe singleton subscriber and the monotonic `id` assigned at registration\ntime. `Drop` removes the matching `(id, LogSink)` entry via\n`Vec::retain`.\n\n# Concurrency\n\n`Send + Sync`. `Drop::drop` acquires the subscriber's `sinks` mutex\nonce, using the poison-recovery policy: a panic in another thread that\nleft the lock poisoned does NOT prevent unregistration. The drop\nraces safely with concurrent `CardEventBus::publish` fan-outs because\nthe publisher takes a snapshot clone of the sinks vec before iterating;\nan in-flight publish may or may not observe the just-unregistered sink,\nbut never deadlocks and never observes torn state.\n\nDropping the guard is O(N) in the number of registered sinks (typically\n== active session count). Callers must not hold the returned guard\nacross `.await` points that could execute on a runtime worker also\nrunning the fan-out — with the current single-shot lock scope this is\nnot a hazard."
    },
    {
      "path": "algocline_engine::card::OrderKey",
      "kind": "struct",
      "docs": "Parsed sort key: path with optional descending flag."
    },
    {
      "path": "algocline_engine::card::PerSubscriber",
      "kind": "struct",
      "docs": "Per-subscriber counter state. Held inside `SubscriberStats` under a\n`Mutex`; `snapshot` clones the fields into an owned `SubscriberHealthRow`\nwhile the lock is held, so the lock window stays short."
    },
    {
      "path": "algocline_engine::card::Predicate",
      "kind": "enum",
      "docs": "Parsed predicate tree."
    },
    {
      "path": "algocline_engine::card::RunSection",
      "kind": "struct",
      "docs": "Optional `[run]` section carrying strategy execution outcome.\n\n* `status` is REQUIRED when the section is present.\n* `reason` / `action` are OPTIONAL free-form strings; when absent they\n  are omitted from the serialized TOML entirely (`skip_serializing_if`).\n\nUsed only for input validation at the Lua bridge boundary — the actual\nCard TOML is written via the raw `serde_json::Value` path so that other\ntop-level fields are preserved verbatim."
    },
    {
      "path": "algocline_engine::card::RunStatus",
      "kind": "enum",
      "docs": "Status of a strategy run, recorded in the `[run]` section of a Card.\n\nThe three variants are intentionally exhaustive so that downstream\nanalysers can pattern-match without a catch-all arm.  Serde uses\nsnake_case so that TOML values round-trip through the Lua bridge\n(`succeeded` / `failed` / `skipped`)."
    },
    {
      "path": "algocline_engine::card::SCHEMA_VERSION",
      "kind": "constant"
    },
    {
      "path": "algocline_engine::card::SamplesQuery",
      "kind": "struct",
      "docs": "Query parameters for `read_samples`."
    },
    {
      "path": "algocline_engine::card::SinkBackfillReport",
      "kind": "struct",
      "docs": "Result of a [`card_sink_backfill`] run. One row per card the tool\ntouched (classified as pushed / skipped / failed / pushed_samples).\nThe `failed` entries carry the error message so an operator can\ntriage read-only mounts etc."
    },
    {
      "path": "algocline_engine::card::SubscriberHealthRow",
      "kind": "struct",
      "docs": "Snapshot row for a single subscriber, serialized directly into the\n`alc_stats` JSON output as one element of the `card_sinks` array."
    },
    {
      "path": "algocline_engine::card::SubscriberStats",
      "kind": "struct",
      "docs": "Process-wide per-subscriber statistics, keyed by subscriber URI\n(the value returned by `CardSubscriber::describe`)."
    },
    {
      "path": "algocline_engine::card::Summary",
      "kind": "struct",
      "docs": "Summary row for `alc.card.list()`."
    },
    {
      "path": "algocline_engine::card::alias_list_with_store",
      "kind": "function",
      "docs": "List aliases from `store`, optionally filtered by pkg."
    },
    {
      "path": "algocline_engine::card::alias_set_with_store",
      "kind": "function",
      "docs": "Bind (or rebind) an alias to a Card in `store`.\n\nValidates that `card_id` exists. If an alias with the same `name` already\nexists it is overwritten — the alias table is intentionally mutable even\nthough the Cards themselves are not."
    },
    {
      "path": "algocline_engine::card::aliases_to_json",
      "kind": "function"
    },
    {
      "path": "algocline_engine::card::append_with_store",
      "kind": "function",
      "docs": "Append new top-level fields to an existing Card.\n\nSemantics: **additive only**. If any top-level key in `fields` already\nexists in the Card, the call fails — Cards are immutable w.r.t. existing\ndata. New top-level keys are inserted and the Card file is rewritten\natomically.\n\nReturns the merged Card JSON."
    },
    {
      "path": "algocline_engine::card::card_sink_backfill_with_store",
      "kind": "function",
      "docs": "Backfill one subscriber (`sink` URI) from the primary store.\n\nSteps:\n1. Look up the target subscriber on the event bus; fail fast if\n   the URI is not registered so the caller gets an immediate\n   error rather than a silent no-op.\n2. Enumerate every `(pkg, card_id)` pair from the default\n   [`CardStore`].\n3. For each pair, ask the subscriber whether it already has that\n   card (`CardSubscriber::has_card`). If yes → skipped (drift-safe,\n   no overwrite). If no → read the primary TOML and `publish_to`\n   the one target sink. Samples are mirrored the same way.\n4. `dry_run = true` short-circuits step 3: the report lists\n   what would have been pushed but no `publish_to` is issued,\n   so `SubscriberStats` does not increment.\n\n`card_sink_backfill` operates with an injectable [`CardStore`]; tests\ndrive this directly against a tempdir-backed [`FileCardStore`] so they\nnever touch the user's real cards directory."
    },
    {
      "path": "algocline_engine::card::create_with_store",
      "kind": "function",
      "docs": "Create a new Card backed by `store`."
    },
    {
      "path": "algocline_engine::card::eval_predicate",
      "kind": "function",
      "docs": "Evaluate a predicate tree against a full Card JSON."
    },
    {
      "path": "algocline_engine::card::event_bus",
      "kind": "function",
      "docs": "Return the process-wide `CardEventBus` singleton, initializing it\non the first call from the `ALC_CARD_SINKS` environment variable."
    },
    {
      "path": "algocline_engine::card::find_with_store",
      "kind": "function",
      "docs": "Filter/sort Cards across the store using the `where` DSL.\n\nWhen no `where` clause is specified and `order_by` only references\nsummary-level fields, uses the lightweight `list_with_store` path to\navoid loading full TOML.  Otherwise loads full TOML per Card."
    },
    {
      "path": "algocline_engine::card::get_by_alias_with_store",
      "kind": "function",
      "docs": "Resolve an alias name to its bound Card and return the full Card JSON.\n\nShortcut for `alias_list → filter → get`. Returns `None` when the alias\ndoes not exist. Errors when the alias points at a missing Card — that\nwould indicate a corrupt alias table (the target was deleted out of band)."
    },
    {
      "path": "algocline_engine::card::get_with_store",
      "kind": "function",
      "docs": "Read a Card from `store` by id. Returns None if not found."
    },
    {
      "path": "algocline_engine::card::import_from_dir_with_store",
      "kind": "function",
      "docs": "Import Card files into `store` from `source_dir` under `pkg`.\n\nCopies `*.toml` and `*.samples.jsonl` files. Existing cards with the\nsame id are skipped (first-writer wins — Card immutability).\n\nReturns `(imported, skipped)` card_id lists."
    },
    {
      "path": "algocline_engine::card::init_event_bus",
      "kind": "function",
      "docs": "Eagerly initialize the bus. Idempotent and safe to call multiple\ntimes; intended for startup hooks (`main.rs`) so that subscriber\nregistration `tracing::info!` lines are emitted at boot rather than\non the first Card write."
    },
    {
      "path": "algocline_engine::card::lineage_to_json",
      "kind": "function",
      "docs": "Render a LineageResult as JSON for the service layer."
    },
    {
      "path": "algocline_engine::card::lineage_with_store",
      "kind": "function",
      "docs": "Walk the lineage tree from `q.card_id` in `store`."
    },
    {
      "path": "algocline_engine::card::list_with_store",
      "kind": "function",
      "docs": "List cards from `store`. `pkg_filter = Some(\"name\")` restricts to that pkg subdir."
    },
    {
      "path": "algocline_engine::card::log_sink_subscriber",
      "kind": "function",
      "docs": "Return the process-wide [`LogSinkCardSubscriber`] singleton.\n\nOn first call, initializes the singleton via\n`OnceLock::get_or_init` and registers it on the [`CardEventBus`]\nthrough [`CardEventBus::add_subscriber`]. Subsequent calls return the\nalready-initialized `Arc`.\n\n# Concurrency\n\n`OnceLock` guarantees the initializer runs exactly once even under\nconcurrent first-callers. The initializer calls `event_bus()` (a\ndistinct `OnceLock`); this cross-cell reentrance is safe because the\ntwo cells are independent (see std::sync::OnceLock docs — reentrance\ndeadlock only applies to the same cell). Callers holding the returned\n`Arc` may drop it freely; the singleton itself lives for `'static`."
    },
    {
      "path": "algocline_engine::card::parse_order_by",
      "kind": "function",
      "docs": "Parse an order_by JSON value.  Accepts:\n  - a string: `\"stats.pass_rate\"` or `\"-stats.pass_rate\"`\n  - an array of strings: `[\"-stats.pass_rate\", \"created_at\"]`"
    },
    {
      "path": "algocline_engine::card::parse_where",
      "kind": "function",
      "docs": "Parse a `where` JSON value into a `Predicate`.\n\n`prefix` is the current nested-key path as we descend through\nsection objects."
    },
    {
      "path": "algocline_engine::card::publish",
      "kind": "function",
      "docs": "Convenience wrapper: publish through the singleton."
    },
    {
      "path": "algocline_engine::card::read_samples_with_store",
      "kind": "function",
      "docs": "Read per-case samples from `{card_id}.samples.jsonl`.\n\nStreams the JSONL file line by line; rows are parsed, optionally\nfiltered by `q.where_`, then paged by `offset` + `limit`.  Offset\napplies to the **post-filter** stream, matching Prisma/SQL\nsemantics.\n\nReturns an empty Vec if no samples file exists (Cards without\nper-case details are the common case, not an error)."
    },
    {
      "path": "algocline_engine::card::register_log_sink",
      "kind": "function",
      "docs": "Register `sink` with the process-wide [`LogSinkCardSubscriber`] and\nreturn an RAII guard.\n\nThe returned [`LogSinkRegistration`] MUST be kept alive for the lifetime\nof the owning `Session`. Dropping the guard unregisters the sink; the\nsink itself (`Arc<Mutex<VecDeque<LogEntry>>>`) is not mutated by the\ndrop.\n\nThis function is infallible. Internally it assigns a monotonically\nincreasing `u64` id via `AtomicU64::fetch_add(1, Ordering::Relaxed)`\n(Relaxed is sufficient because the id only becomes visible to other\nthreads once the subsequent `sinks.lock()` push publishes it).\n\nOn first call, this transitively initializes the singleton\n[`LogSinkCardSubscriber`] and registers it on the [`CardEventBus`] via\n[`log_sink_subscriber`].\n\n# Concurrency\n\nCancel-safe. `Send + Sync` arguments; safe to call from any thread\nconcurrently. Two concurrent calls receive distinct ids."
    },
    {
      "path": "algocline_engine::card::subscriber_stats_snapshot",
      "kind": "function",
      "docs": "Public entry point: snapshot of all process-wide subscriber stats.\nWrapper around `event_bus().stats().snapshot()` so that downstream\ncrates (notably `algocline-app`) do not need a handle to the\n`CardEventBus` singleton."
    },
    {
      "path": "algocline_engine::card::summaries_to_json",
      "kind": "function"
    },
    {
      "path": "algocline_engine::card::validate_name",
      "kind": "function"
    },
    {
      "path": "algocline_engine::card::write_samples_with_store",
      "kind": "function",
      "docs": "Write per-case samples to `{card_id}.samples.jsonl` (write-once).\n\nEach `samples` entry is serialized as one compact JSON line.\nFails if a samples file already exists for this card — mirrors\nthe immutability guarantee of Cards themselves."
    },
    {
      "path": "algocline_engine::card_context",
      "kind": "module",
      "docs": "Card context injection — resolves Card summaries for prompt injection.\n\nPhase 3-F MVP: `alc.llm(prompt, {card_context = ...})` opts key wires\na small set of prior Cards into the LLM system prompt as an XML-like\n`<past_cards>` block.  This module is the pure-Rust core: Lua bridge\nwiring lives in `bridge/llm.rs`.\n\n## Public API\n\n* [`CardContextSpec`] — 2-form spec (`CardId` or `Query { pkg, limit }`)\n  received from the Lua bridge after opts extraction.\n* [`resolve`] — spec → `Vec<Json>` full Card resolution against a\n  `CardStore`.  Silent Ok(empty) on not-found / empty query.\n* [`format_past_cards`] — infallible fixed-template renderer producing\n  the `<past_cards>...</past_cards>` block.\n\n## Fixed format template (MVP)\n\n```text\n<past_cards>\nCard MM/DD pkg=<pkg> card_id=<id> [run.status=<status>] Rating <val> reason=<reason>\n...\n</past_cards>\n```\n\n* Each Card on 1 line, `created_at` desc order (newest first)\n* `[run.status=...]` emitted only when the Card JSON carries `run.status`\n* `Rating <val>` emitted only when `stats.pass_rate` is present\n  (formatted with 1 decimal digit)\n* `reason=<reason>` emitted only when `run.reason` is present\n* `MM/DD` derived from RFC 3339 `created_at` (`&s[5..10]` with the\n  `-` separator rewritten to `/`); omitted when `created_at` is\n  missing or too short\n* Empty input slice → empty string (no `<past_cards>` wrapper)\n\n## Error handling\n\n`resolve` returns `Result<Vec<Json>, String>`.  Underlying\n`card::get_with_store` / `card::find_with_store` errors are re-wrapped\nwith a `\"card_context resolve: \"` prefix so the bridge layer can\nsurface them via `tracing::warn!` before silently dropping the inject\n(Phase 3-F Done Criteria #4: silent no-op on resolution failure)."
    },
    {
      "path": "algocline_engine::card_context::CardContextSpec",
      "kind": "enum",
      "docs": "Card context specification extracted from the Lua `card_context` opt.\n\nTwo forms are accepted at the Lua boundary:\n\n* `card_context = \"<card_id>\"` → [`CardContextSpec::CardId`]\n* `card_context = { pkg = \"<name>\", limit = N }` → [`CardContextSpec::Query`]\n\nThe Lua bridge is responsible for the extraction; this module treats\nthe parsed enum as-is."
    },
    {
      "path": "algocline_engine::card_context::format_past_cards",
      "kind": "function",
      "docs": "Render a slice of resolved Card JSON values as the fixed\n`<past_cards>` XML-like block.\n\nInfallible: unknown / missing fields are silently omitted from the\nper-Card line rather than surfaced as errors.  Empty input yields an\nempty string so the caller can unconditionally prefix without\nproducing a stray `<past_cards></past_cards>` wrapper."
    },
    {
      "path": "algocline_engine::card_context::resolve",
      "kind": "function",
      "docs": "Resolve a [`CardContextSpec`] against `store`, returning full Card\nJSON documents.\n\n* [`CardContextSpec::CardId`] → 1-element `Vec<Json>` on hit, empty\n  `Vec<Json>` on miss (miss is Ok, not Err).\n* [`CardContextSpec::Query`] → up to `limit` Card JSON values ordered\n  by `created_at` descending; empty result set is Ok.\n\nThe query form loads full TOML for each hit via a second\n`get_with_store` call (N+1 fetch).  With the MVP default `limit = 5`\nthis is well within I/O budget; if a future phase needs to raise the\nlimit substantially we can add a `find_full_with_store` batching API."
    },
    {
      "path": "algocline_engine::execution",
      "kind": "module",
      "docs": "`algocline-engine::execution` — v2 execution module.\n\nProvides the engine-level session lifecycle machinery:\n\n- [`SessionRecord`] — per-session ownership bundle (state, broadcast tx,\n  cancellation token, join handle, response senders).\n- [`SessionRegistryV2`] — registry that spawns, tracks, and controls v2\n  sessions (coexists with the legacy `SessionRegistry` in `session.rs`).\n- [`driver_loop`] — background async fn that drives a session to completion,\n  embedding the four cooperative cancellation checkpoints (Crux R2).\n- [`BroadcastObserverHandle`] — concrete [`algocline_core::execution::ObserverHandle`]\n  implementation backed by a `broadcast::Receiver<ProgressEvent>` (Crux R3).\n\n# Design invariants\n\n- **Crux R1**: No `rmcp::*`, `progressToken`, `_meta`, `notifications/*`, or\n  `mcp_`-prefixed identifiers in this module tree.\n- **Crux R2**: Cancellation uses `CancellationToken::cancel()` only;\n  `JoinHandle::abort()` and process-kill paths are absent.\n- **Crux R3**: `observe()` is sync and returns a valid handle even with zero\n  pre-registered observers (sink-free fan-out)."
    },
    {
      "path": "algocline_engine::session",
      "kind": "module",
      "docs": "Session-based Lua execution with pause/resume on alc.llm() calls.\n\nRuntime layer: ties Domain (ExecutionState) and Metrics (ExecutionMetrics)\ntogether with channel-based Lua pause/resume machinery."
    },
    {
      "path": "algocline_engine::session::DEFAULT_PROMPT_PREVIEW_CHARS",
      "kind": "constant",
      "docs": "Default preview length (chars) used when `PendingFilter::preset_preview()`\nis constructed without an explicit length. Env var\n`ALC_PROMPT_PREVIEW_CHARS` (resolved in `AppConfig`) overrides this."
    },
    {
      "path": "algocline_engine::session::ExecutionResult",
      "kind": "struct",
      "docs": "Session completion data: terminal state + metrics."
    },
    {
      "path": "algocline_engine::session::FeedResult",
      "kind": "enum",
      "docs": "Result of a session interaction (start or feed)."
    },
    {
      "path": "algocline_engine::session::PendingFilter",
      "kind": "struct",
      "docs": "Per-field filter controlling which `LlmQuery` attributes are projected\ninto a Snapshot's `pending` array.\n\nAdding a new field to `LlmQuery` only requires adding one matching\n`bool` here — the shape stays stable so API surface does not grow\nenum variants for every new attribute."
    },
    {
      "path": "algocline_engine::session::PromptProjection",
      "kind": "enum",
      "docs": "Prompt projection mode — 3 states rather than a bool so that truncation\nlength can travel inside the filter object.\n\nJSON tag is `mode`: `{\"mode\":\"off\"}` / `{\"mode\":\"preview\",\"chars\":200}` /\n`{\"mode\":\"full\"}`."
    },
    {
      "path": "algocline_engine::session::Session",
      "kind": "struct",
      "docs": "A Lua execution session with domain state tracking.\n\nEach session owns a dedicated Lua VM via `_vm_driver`. The VM's OS thread\nstays alive as long as the driver is held, and exits cleanly when the\nsession is dropped (channel closes → Lua thread drains and exits)."
    },
    {
      "path": "algocline_engine::session::SessionError",
      "kind": "enum"
    },
    {
      "path": "algocline_engine::session::SessionRegistry",
      "kind": "struct",
      "docs": "Manages active sessions.\n\n# Locking design (lock **C**)\n\nUses `tokio::sync::Mutex` because `feed_response` holds the lock\nwhile calling `Session::feed_one()` (which itself acquires the\nper-session `std::sync::Mutex<SessionStatus>`, lock **A**). The lock\nordering invariant is always **C → A** — no code path acquires A\nthen C, so deadlock is structurally impossible.\n\n`tokio::sync::Mutex` is chosen here (rather than `std::sync::Mutex`)\nbecause `feed_response` must take the session out of the map for\nthe async `wait_event()` call. The two-phase pattern (lock → remove\n→ unlock → await → lock → reinsert) requires an async-aware mutex\nto avoid holding the lock across the `wait_event().await`.\n\n## Contention\n\n`list_snapshots()` (from `alc_status`) holds lock C while iterating\nall sessions. During this time, `feed_response` for any session is\nblocked. Given that snapshot iteration is O(n) with n = active\nsessions (typically 1–3) and each snapshot takes microseconds, this\nis acceptable. If session count grows significantly, consider\nswitching to a concurrent map or per-session locks.\n\n## Interaction with lock A\n\n`Session::snapshot()` (called under lock C in `list_snapshots`)\nacquires lock A via `ExecutionMetrics::snapshot()`. This is safe:\n- Lock order: C → A (consistent with `feed_response`)\n- Lock A hold time: microseconds (JSON field reads)\n- Lock A is per-session (no cross-session contention)"
    },
    {
      "path": "algocline_engine::state",
      "kind": "module",
      "docs": "Persistent key-value state backed by JSON files.\n\n## Architecture\n\nAll state operations go through the [`StateStore`] trait, which\nabstracts the storage backend.  The default implementation,\n[`JsonFileStore`], persists each namespace as a JSON file under a\ncaller-provided root directory with atomic writes (tmp + rename).\n\n## Tier 1 — Current API\n\n| Operation | Description |\n|-----------|-------------|\n| `get` | Read a value (returns `None` if absent) |\n| `set` | Write a value (upsert) |\n| `delete` | Remove a key (returns whether it existed) |\n| `keys` | List all keys in a namespace |\n| `has` | Check existence (cost is backend-dependent) |\n| `set_nx` | Set-if-not-exists (returns `false` if key already present) |\n| `incr` | Counter increment — single-process atomic (read-modify-write) |\n\n## Tier 2 — Future Extensions (design notes, not yet implemented)\n\nThe following operations are planned but **not yet implemented**.\nThe trait is designed to accommodate them without breaking changes.\nReview this list when adding a new backend.\n\n- **TTL**: `set(key, value, opts)` with `opts.ttl_secs`, plus\n  `ttl(key) -> Option<Duration>` to query remaining time.  Useful\n  for caching patterns (e.g. Hub index cache, LLM response cache).\n- **Batch**: `mget(keys) -> Vec<Option<Value>>` and\n  `mset(pairs) -> Result<()>`.  Reduces I/O round-trips for\n  file/network backends.\n- **clear**: Flush all keys in a namespace.  OpenResty's\n  `flush_all` equivalent.\n\n## Backend Swappability\n\nBecause the engine interacts with state only through the\n[`StateStore`] trait, backends can be swapped without changing Lua\ncode.  Planned backends:\n\n- `JsonFileStore` (current, default)\n- In-memory `HashMap` (for tests and short-lived sessions)\n- SQLite (for larger datasets with indexed queries)\n- Redis (for distributed / multi-process scenarios)"
    },
    {
      "path": "algocline_engine::state::JsonFileStore",
      "kind": "struct",
      "docs": "JSON-file-backed state store.\n\nEach namespace is a single JSON file at\n`{root}/{namespace}.json`.  Writes are atomic: the new state is\nwritten to a `.tmp` sibling and then renamed.\n\nThe root directory is provided at construction time; callers are\nexpected to resolve it from the service-layer `AppDir` abstraction\n(typically `~/.algocline/state/`).\n\n## Concurrency\n\nPer-namespace locks (`std::sync::Mutex`) prevent lost updates under\nconcurrent `alc.state.*` calls within the same process.  The lock\nis acquired for the full load → mutate → atomic-rename cycle, so\ntwo tokio tasks operating on the **same** namespace are serialised.\n\nRationale for `std::sync::Mutex` over `tokio::sync::Mutex`:\nall I/O inside the lock uses `std::fs` (synchronous, no `.await`),\nso a standard mutex is sufficient and avoids holding a tokio mutex\nacross potential scheduler context switches.\n\n**Multi-process safety is NOT provided.**  If multiple `alc`\nprocesses share the same state directory (uncommon), use a backend\nwith native `INCR` (Redis) or transactions (SQLite)."
    },
    {
      "path": "algocline_engine::state::ResetReport",
      "kind": "struct",
      "docs": "Report returned by [`JsonFileStore::reset_dispatched_with_backup`]\ndescribing what was modified."
    },
    {
      "path": "algocline_engine::state::StateError",
      "kind": "enum",
      "docs": "Errors returned by the dispatched-layout helpers\n(`list_dispatched`, `show_dispatched`, `reset_dispatched_with_backup`).\n\nUnlike the legacy [`StateStore`] trait methods (which return `String`\nerrors), these helpers use a typed enum so callers can distinguish\nmissing-key from I/O failure at the type level without pattern-matching\non OS error codes."
    },
    {
      "path": "algocline_engine::state::StateStore",
      "kind": "trait",
      "docs": "Backend-agnostic key-value state store.\n\nAll operations are namespace-scoped.  Implementations must be\n`Send + Sync` so they can be shared across Lua VMs (e.g. fork)."
    }
  ]
}