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
7 changes: 4 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,10 @@ gateway; engine stays local, Phase 2) — split across build phase 5 (managed in
phase 6 (cloud execution + portal); the engine is identical across all three (ADR-0012..0015).
**Status: Phase 1 in progress — milestone M1 (LLM seam proven) reached (PR #9, 2026-06-07);**
`@relavium/llm` (the seam + all three adapters) is landed and green. Phase 0 (M0) landed
the monorepo + `@relavium/shared` + CI + `@relavium/db`. Active work continues on the
`FallbackChain` (1.K) and the
[`@relavium/core` engine](docs/roadmap/phases/phase-1-engine-and-llm.md); see
the monorepo + `@relavium/shared` + CI + `@relavium/db`. Since then, the ADR-0031 multimodal
seam-shape amendment (1.AD, PR #11) and the `FallbackChain` runner (1.K, PR #13, 2026-06-11)
have landed. Active work continues on the
[`@relavium/core` engine](docs/roadmap/phases/phase-1-engine-and-llm.md) (1.L next); see
[docs/roadmap/current.md](docs/roadmap/current.md).

## The non-negotiable rules
Expand Down
9 changes: 5 additions & 4 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,16 +38,17 @@ A run executes in one of **three execution modes** behind the one `LLMProvider`
engine is identical across all three. See [ADR-0012](docs/decisions/0012-managed-inference-dual-mode.md) to [ADR-0015](docs/decisions/0015-managed-mode-data-handling-and-compliance.md)
and [docs/architecture/managed-inference.md](docs/architecture/managed-inference.md).

**Status: Phase 1 in progress — milestone M1 (LLM seam proven) reached (PR #9, 2026-06-07).**
**Status: Phase 1 in progress — milestone M1 (LLM seam proven) reached (PR #9, 2026-06-07); the `FallbackChain` runner (1.K) has since landed, completing 1.m2 with the cost tracker (PR #13, 2026-06-11).**
Phase 0 (M0, 2026-06-04) landed the monorepo, strict toolchain + CI, `@relavium/shared` (the
full Zod contract set), the no-vendor-type seam fence, and `@relavium/db`. Phase 1 has since
landed `@relavium/llm` — the `LLMProvider` seam + all three adapters (Anthropic, OpenAI/DeepSeek,
Gemini), green on one shared conformance suite with no vendor type crossing the seam, plus the
ADR-0030 seam-shape amendment — and the **ADR-0031 multimodal seam-shape amendment (1.AD, PR #11,
2026-06-10)**: the media content/stream union members, the per-modality capability matrix, and the
reserved generator methods, shape-only, landed before the seam's exhaustive consumers. Active work
continues on the `FallbackChain` (1.K) and the
[`@relavium/core` engine](docs/roadmap/phases/phase-1-engine-and-llm.md); see
reserved generator methods, shape-only, landed before the seam's exhaustive consumers. The
`FallbackChain` runner (1.K, PR #13) is now landed and fully covered; active work continues on the
[`@relavium/core` engine](docs/roadmap/phases/phase-1-engine-and-llm.md), starting with the YAML
parser (1.L); see
[docs/roadmap/current.md](docs/roadmap/current.md). See [README.md](README.md) for the public overview.

## Non-negotiable rules for AI agents
Expand Down
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ OpenAI + DeepSeek; Gemini), passing one shared conformance suite behind the froz
vendor type crossing it (PR #7–#9), followed by the **ADR-0031 multimodal seam-shape amendment
(1.AD)** — the media content/stream union members, the per-modality capability matrix, and the
reserved generator methods, landed **shape-only** before the seam's exhaustive consumers exist
(PR #11, 2026-06-10). Next on the critical path: the `FallbackChain` runner (1.K)
and the `@relavium/core` engine — parser, run loop, checkpoint/resume, retry, plus the **AgentSession**
runtime + export-to-workflow sub-spine. See [docs/roadmap/current.md](docs/roadmap/current.md) for
(PR #11, 2026-06-10). The seam's last policy layer — the `FallbackChain` runner (1.K) — then landed
(PR #13, 2026-06-11), completing the LLM lane. Next on the critical path: the `@relavium/core` engine
— parser, run loop, checkpoint/resume, retry, plus the **AgentSession** runtime + export-to-workflow
sub-spine. See [docs/roadmap/current.md](docs/roadmap/current.md) for
live status.
104 changes: 104 additions & 0 deletions docs/decisions/0035-yaml-parser-dependency.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
# ADR-0035: YAML parser for the engine — the `yaml` package, confined to `@relavium/core`

- **Status**: Accepted
- **Date**: 2026-06-11
- **Related**: [ADR-0003](0003-pure-ts-engine-not-langgraph-python.md), [ADR-0009](0009-git-native-workflow-yaml.md), [ADR-0011](0011-internal-llm-abstraction.md), [ADR-0020](0020-zod-runtime-schema-library.md), [ADR-0023](0023-strict-authored-yaml-validation.md), [ADR-0033](0033-strict-config-files-amends-0023.md), [workflow-yaml-spec.md](../reference/contracts/workflow-yaml-spec.md), [architectural-principles.md](../standards/architectural-principles.md), [tech-stack.md](../tech-stack.md)

## Context

Workflows and agents are authored as **git-committable YAML** ([ADR-0009](0009-git-native-workflow-yaml.md)).
The engine's first workstream — `WorkflowYAMLParser` (1.L) — must turn a `.relavium.yaml`
**string** into a plain JS object before validating it against the strict `@relavium/shared`
`WorkflowSchema` ([ADR-0023](0023-strict-authored-yaml-validation.md)). `@relavium/shared`
deliberately validates **already-parsed objects only** (its round-trip test notes "YAML→object parsing
is `@relavium/core`'s responsibility" and round-trips a JS object, not a YAML string), so the
YAML-string→object step is unowned until 1.L — and it serves both the workflow parser and the future
**agent**-YAML parser, so the decode + syntax-error layer is authored to be document-agnostic.

There is **no YAML library pinned anywhere** in the workspace: `tech-stack.md` lists none, the pnpm
catalog has no entry, and no package depends on one (the only `yaml`/`js-yaml` entries in the lockfile
are dev-tooling transitives of ESLint/Prettier — not a sanctioned runtime dependency). Adding one is a
**new engine runtime dependency**, which the no-new-runtime-dependency-without-an-ADR rule
(CLAUDE.md non-negotiable #2; [architectural-principles.md](../standards/architectural-principles.md) §9)
requires be decided here, **before** any implementation — and paired in the same change with the
`tools/engine-deps/check.mjs` allowlist edit the guard demands.

Two constraints shape the choice. (1) **Engine purity** (CLAUDE.md rule #5, [ADR-0003](0003-pure-ts-engine-not-langgraph-python.md),
[ADR-0011](0011-internal-llm-abstraction.md)): `@relavium/core` runs identically in Node, the Tauri
WebView, the VS Code extension host, and Bun, so the parser must be **pure-JS with zero native bindings**
— a native-addon YAML library would break the WebView surface and need a `pnpm.onlyBuiltDependencies`
install-script entry — and the decode must be **deterministic across surfaces** (the same file must
produce the same plain object in every runtime). (2) **Build-in-house targets
the core** (engine, LLM seam, schedulers), *not* interop-format plumbing — a YAML 1.2 tokenizer is
exactly the commodity, security-sensitive primitive the "never hand-roll, wrap a vetted library" rule
([ADR-0019](0019-cli-node-keychain-library.md) set the precedent) says to adopt rather than write.

## Decision

**We will parse authored YAML with the [`yaml`](https://eemeli.org/yaml/) package (eemeli/yaml), as a
runtime dependency of `@relavium/core` only, pinned through the pnpm `catalog:` — see
[tech-stack.md](../tech-stack.md) for the version.**

- The parser is configured with an explicit **hardened, deterministic profile**, not the library
defaults. The default `parse(text)` resolves `!!timestamp`→`Date`, `!!binary`→`Buffer`/`Uint8Array`,
YAML-1.1 `<<` merge keys, complex (non-string) map keys, and anchor/alias graphs — any of which would
make the same file decode to **different objects on different surfaces** (Node vs WebView) and break
checkpoint/export/serialization. The pinned options are `version: '1.2'`, `schema: 'core'` (a date- or
binary-like scalar stays a **string** — no `Date`/`Buffer`), `resolveKnownTags: false`, `merge: false`,
`uniqueKeys: true`, `stringKeys: true`, `maxAliasCount: 0` (**anchors/aliases are not part of the
authored contract** — this forecloses the billion-laughs / recursive-alias expansion class outright),
and `prettyErrors: false` + `logLevel: 'error'` (no source snippet in the message, no `console.warn` —
the parser is a pure function). A `%YAML 1.1` directive is **not honoured**: the 1.2 core profile is
pinned regardless. The decode is further guarded by a pre-parse source-size cap, and **every**
parse-stage throw (a YAML fault, or the anchor/alias `ReferenceError` that `maxAliasCount: 0`
raises) is normalized to a typed, secret-free `WorkflowSyntaxError` whose line/column come from a
`LineCounter`, never the source text. With this
profile the decode yields only plain JSON-like data, and **strict Zod then enforces the contract**
([ADR-0023](0023-strict-authored-yaml-validation.md) / [ADR-0033](0033-strict-config-files-amends-0023.md))
on that data.
- The dependency is **confined to `@relavium/core`** — the document-agnostic decode + `WorkflowSyntaxError`
layer serves the workflow parser today and the agent-YAML parser later. It is added to
`ENGINE_ALLOWLISTS['packages/core']` in `tools/engine-deps/check.mjs` in this same change, is **not**
added to `@relavium/shared` (which stays Zod-only), and needs **no** `onlyBuiltDependencies` entry (pure-JS).

Considered alternatives:

- **`js-yaml`** (rejected) — also pure-JS and viable; the choice is on measurable grounds. `yaml` ships a
documented CST/AST and a `LineCounter` for precise, source-free diagnostics (which the field-named
errors and the future VS Code language server need), exposes the alias-count, schema, and string-key
controls the hardened profile above depends on, carries zero transitive runtime dependencies with a
first-class browser build, and has a steadier recent release cadence. `js-yaml` would still need this
same ADR + allowlist edit.
- **Hand-roll a YAML subset parser** (rejected) — YAML is a deceptively large, security-sensitive grammar
(anchors, aliases, billion-laughs); hand-rolling buys no differentiation, adds a permanent
spec-tracking burden, and is exactly the class of commodity primitive the build-in-house rule excludes.
- **Reuse the transitive `js-yaml` in the lockfile** (rejected) — it is dev-tooling, absent from the
catalog, the engine allowlist, and the install allowlist; relying on it would bypass rule #2 and is
precisely what the engine-deps guard exists to catch.
- **Parse YAML in `@relavium/shared`** (rejected) — `@relavium/shared` is the platform-free contract
layer with Zod as its sole runtime dependency; the parse step belongs to the engine that consumes it.

## Consequences

### Positive

- 1.L gets a decided, pure-JS dependency before any code is written — no ad-hoc adoption inside a
feature PR — and the engine stays runnable on every surface (Node, Tauri WebView, VS Code host, Bun).
- The library tracks YAML 1.2 for us and gives precise syntax positions; the strict Zod schema remains
the single source of validation truth, so the parser maps faithful objects → field-named errors.
- The **hardened profile** makes the decode deterministic across surfaces (plain JSON-like data only),
forecloses the alias-expansion DoS class (`maxAliasCount: 0` + a size cap), and keeps every parse
failure a typed, secret-free `WorkflowSyntaxError` — so a cloned repo's file can neither crash the
engine before Zod runs nor leak its content into an error.
- The dependency is quarantined to `@relavium/core`'s manifest and asserted by the engine-deps guard;
`@relavium/shared` stays Zod-only.

### Negative

- A new runtime dependency in the engine — mitigated by it being pure-JS, catalog-pinned, confined to
the one package the guard enforces, and a commodity the build-in-house rule explicitly excludes from
"core".
- The profile deliberately rejects YAML features authors might expect from other tools — anchors/aliases,
`<<` merge keys, `%YAML 1.1`, `!!`-tagged Dates/binaries. Accepted: these add cross-surface
non-determinism or a DoS surface for no authoring benefit; authored Relavium files are small and
hand-written, and a rejected file fails fast with a typed, field-named error.
1 change: 1 addition & 0 deletions docs/decisions/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ flowchart TD
| 0032 | [Desktop Rust-side media de-inlining on the egress path (amends 0018)](0032-desktop-rust-media-de-inline-amends-0018.md) | Accepted | 2026-06-08 |
| 0033 | [Local config files are strictly validated too (amends 0023)](0033-strict-config-files-amends-0023.md) | Accepted | 2026-06-09 |
| 0034 | [MCP client implementation — the official TypeScript SDK, scheduled in build phase 2](0034-mcp-client-sdk-dependency.md) | Accepted | 2026-06-10 |
| 0035 | [YAML parser for the engine — the `yaml` package, confined to `@relavium/core`](0035-yaml-parser-dependency.md) | Accepted | 2026-06-11 |

## Creating a new ADR

Expand Down
5 changes: 3 additions & 2 deletions docs/reference/shared-core/llm-provider-seam.md
Original file line number Diff line number Diff line change
Expand Up @@ -293,8 +293,9 @@ adapters):
`signature` is never persisted to a session, never replayed across a provider
boundary on fallback, and never written to a run event or log — the engine does
not interpret it; only the originating adapter feeds it back (a same-provider,
same-turn obligation owned by the 1.K `FallbackChain` strip-on-failover, not yet
exercised — no consumer beyond the adapters exists).
same-turn obligation enforced by the 1.K `FallbackChain` strip-on-failover, which
drops every `reasoning` part on a cross-provider advance — see the fallback section
below).
- **`responseFormat`** on `LlmRequest` — `{ type: 'text' } | { type: 'json', schema, name?, strict? }`,
one canonical JSON-Schema each adapter lowers to the provider's native
structured-output mode (OpenAI `response_format`, Gemini `responseJsonSchema`,
Expand Down
2 changes: 1 addition & 1 deletion docs/roadmap/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ to that phase's work breakdown).
| ID | Milestone | Product phase | Achieved by (phase · workstreams) |
|----|-----------|---------------|-----------------------------------|
| **M0 ✅** | **Foundations green** *(achieved 2026-06-04)* — monorepo + tooling + CI are green on a clean checkout; `@relavium/shared` exports the full Zod schema set and round-trips the reference YAML with no drift; the canonical `RunEvent` union (`sequenceNumber`, `cost:updated`) is pinned by a type-level + runtime test; the no-vendor-type seam fence is live; `@relavium/db` is scaffolded. | 1 | Phase 0 · 0.A–0.I *(done, PR #1–#3)* |
| **M1 ✅** | **LLM seam proven** *(achieved 2026-06-07, PR #9)* — `@relavium/llm`'s provider-agnostic `LLMProvider` seam with 3 adapters (Anthropic, the OpenAI-compatible adapter serving OpenAI + DeepSeek, Gemini), all passing one shared conformance suite (fixtures on PR, live nightly reserved/pending keys); no vendor SDK type crosses the seam. The `FallbackChain` runner and cost tracker complete just after, at 1.m2. (Built **first** per the engine-first order — the engine's `AgentRunner` depends on it.) | 1 | Phase 1 · `@relavium/llm` seam + 3 adapters — conformance green at **1.J** (1.G–1.J; FallbackChain + cost tracker at 1.m2 / 1.B, 1.K) |
| **M1 ✅** | **LLM seam proven** *(achieved 2026-06-07, PR #9)* — `@relavium/llm`'s provider-agnostic `LLMProvider` seam with 3 adapters (Anthropic, the OpenAI-compatible adapter serving OpenAI + DeepSeek, Gemini), all passing one shared conformance suite (fixtures on PR, live nightly reserved/pending keys); no vendor SDK type crosses the seam. The `FallbackChain` runner (1.K, PR #13) and the cost tracker (1.B) have since landed, completing 1.m2. (Built **first** per the engine-first order — the engine's `AgentRunner` depends on it.) | 1 | Phase 1 · `@relavium/llm` seam + 3 adapters — conformance green at **1.J** (1.G–1.J; FallbackChain + cost tracker at 1.m2 / 1.B, 1.K) |
| **M2** | **Engine end-to-end (critical-path milestone)** — `@relavium/core` parses YAML→DAG and runs it (the `WorkflowEngine` + `AgentRunner` over all node types, emitting canonical events through the `RunEventBus`, with checkpoint/resume, retry, the deterministic expression sandbox, and the pre-egress budget governor), with the M1 seam wired in; a Node harness runs a 3-node workflow end-to-end with live streaming, checkpoint/resume, retry, and a provider fallback. Zero platform-specific imports. | 1 | Phase 1 · `@relavium/core` parser + runner + checkpoint/resume + retry + **expression sandbox (1.AB) + budget governor (1.AC)** + Node harness (1.L–1.U); the agent-first sub-spine (1.V–1.AA) lands in parallel (1.m5) |
| **M3** | **CLI + engine regression harness** — the `relavium` CLI drives the engine end-to-end with a live `ink` TUI and a deterministic `--json` CI mode; a small fixture suite runs in CI on every engine change as the agreed regression gate for Phases 3–6. | 1 | Phase 2 · 2.D (run wiring), 2.F (`--json` / CI mode), 2.K (regression harness) |
| **M4** | **Desktop agent-management center** — a signed, offline, no-account Tauri v2 app: ReactFlow canvas with all node types, live execution theater, gate overlay, local SQLite history (trace/Gantt/replay/retry-from-node), OS keychain; ADR-0010 perf gate + per-platform e2e green. | 1 | Phase 3 · 3.K (packaging), 3.L (e2e + perf gate), 3.P (sign-off) |
Expand Down
Loading
Loading