Skip to content

The core loop

Every interaction with Valved flows through one loop:

Intent → Plan → Build → Run → Deploy → Schedule → Observe

Steps 1–3 are LLM-mediated (subagents reason, grounded in real tool output); steps 4–7 are deterministic mechanics. The loop is the same whether the goal is a new pipeline, a change to an existing dbt model, a config change, or a schedule adjustment — and whether it’s driven by a human at the CLI, an agent via MCP, or a CI workflow via REST.

A user or external agent expresses a goal in natural language — “onboard Salesforce”, “make stg_orders incremental”, “add freshness checks to all marts”. Intent enters via the CLI, REST API, MCP server, or web UI.

The orchestrator (the harness’s main loop) classifies and decomposes the goal, delegates to the right domain subagents, gathers component context, and synthesizes a structured plan. What prints is a design summary and the cost of the planning call itself — not a file diff. The proposed per-engine file list is written to .valved/plans/<plan_id>.json; nothing has been authored yet, so there is nothing to diff.

A plan is a durable, reviewable artifact. It records which engine owns which slice of the work, and that routing is what makes valved build author dbt models rather than a dlt extract-load script.

Terminal window
valved plan "ingest the Stripe charges API into raw_stripe"
valved build <plan_id>

valved plan --refine <plan_id> "<feedback>" records a new plan with parent_plan_id pointing at its predecessor, and a refine inherits its parent’s path. A routed parent — one whose design carries planned_by_engine — refines through the routed path: the same engineers, in the same order, on the same slices, each handed its own prior design plus your feedback. The child comes back with its routing intact, so a dbt goal stays a dbt plan. A parent with no recorded routing refines through the single-engine extract-load planner, unchanged.

Which path a refine takes is decided by what the parent recorded, never re-derived from the feedback text. That is deliberate: re-classifying a fragment like “make it hourly” is exactly how the routing would drift from one hop to the next. It also means the rule cuts both ways — an extract-load plan cannot become a routed plan via --refine either.

This is what makes Valved’s own remedy loop work. When an engineer ends a plan with a question, valved build refuses the plan (exit 2), prints the question, and tells you to answer with valved plan --refine <plan_id> "<answer>". The answer reaches the engine that asked. A peer engine in the same plan receives the answer too, but is not handed a question it did not ask — so a multi-engine plan re-designs coherently instead of one slice drifting while the others are re-derived from a fragment. When every engineer is satisfied, the block clears and the child plan builds.

The answer does not stop at plan time. A refined plan is stored with the original ask and your feedback as its goal, and that is the string valved build hands to the engineer that writes the code — the same string it hands to the review fan-out. Author and reviewer read the same requirement, so the gate can no longer fail a build for missing something the author was never told.

Once approved, the subagents generate code into the relevant component — dlt sources and resources under el/, dbt models and tests under models/, the SQL glue. They verify by executing (dlt pipeline run, dbt build / test) until green. The files land in the working tree, not committed yet.

The composition step is the exception. The pipeline engineer owns pipelines/**, but it is granted edit and not create_file, and edit refuses to create a file that does not exist — so it can modify a pipelines/<name>.toml you already have and cannot author the first one. On a greenfield project you write that file yourself; see step 8 of the Quickstart for a working example.

Terminal window
valved build <plan_id>

Verification is not a dry run. The dbt engineer’s dbt_verify tool runs dbt through the component’s pinned engine, and its build / run / seed / snapshot subcommands issue CREATE OR REPLACE TABLE against the active target — so valved build can write to your warehouse, not only to the working tree. That is the intended behaviour (an engineer that cannot execute cannot honestly claim green), and it is bounded three ways:

  • dbt_verify requires a select argument, so only the authored slice is built — a bare project-wide dbt build is rejected.
  • The materializing subcommands are denied below build permission mode. valved plan clamps its subagents to plan mode, so a design run materializes nothing. dbt test reads and is allowed in any mode.
  • The tool is not granted at all unless a [components.dbt] block pins an engine that valved connect has actually installed.

In orchestration-only mode (you bring existing dlt/dbt/sql), build composes and registers your components by name without generating component code.

The user (or agent, or scheduler) executes the composed pipeline against the dev target. Iteration is cheap — re-run, refine, re-run, until the rows look right.

Terminal window
valved run <pipeline> --watch

Promote the component(s) plus the control-plane composition from dev to prod via a configurable handoff:

files → commit → push → pr (default: pr)

A change spanning a separate-repo component and the control-plane repo produces linked PRs (ingest-first ordering). Valved opens and orders the PRs; you merge.

Terminal window
valved deploy <pipeline> # default handoff (PR)
valved deploy <pipeline> --handoff push # stop at push, no PR

Once merged, the control plane fires runs against prod on the configured cadence. The schedule is data, not code: the live schedule lives in the schedules table and is changed instantly (and audited) via valved schedule — not via a code change or PR. valved schedule set-cron creates the row if it doesn’t exist yet, so a schedule can be stood up end to end from the CLI. See Scheduling & runs.

Every run, log line, status transition, and cost number is queryable via CLI, REST, MCP, or the static UI. External agents can subscribe to run-completion webhooks. Failed runs surface for recovery.

valved ask is the explorer subagent — it handles investigative, read-only questions (“Where do we calculate net revenue?”, “Which models depend on stg_orders?”) and answers with citations. It investigates dbt’s manifest, dlt’s stored schema, your project files, your memory files, and — through its sql grant — your actual warehouse. It does not read the web: the explorer is granted web_fetch, but no fetcher backend is wired in the shipped runtime, so the call returns web_fetch is not configured in this runtime. It runs in the harness’s read_only permission mode, takes no jobs-queue lock, and can run concurrently with anything else.

Terminal window
valved ask "what breaks if I change raw_stripe.charges?"

read_only mode still holds. It blocks every write before the tool executes, and the sql tool has its own floor on top of that: warehouse writes and DDL require deploy mode, which an ask never runs at. An ask reads your warehouse; it cannot change it.