Quickstart
This walks the loop — init → connect → plan → build → run → deploy — for a greenfield project. It assumes you’ve installed Valved and have Docker available.
Read the cautions. Several of these steps have gaps that will quietly hand you the wrong thing if you skim past them.
-
Scaffold a project with a dbt project and a bundled Postgres:
Terminal window mkdir my-valved-project && cd my-valved-projectvalved init --with-dbtdocker compose up -d # start the state storevalved initwritesvalved.toml, thevalved/config dir (connections.toml,models.toml,runner.toml, and the memory files),.env.example,.gitignore,docker-compose.yml, and an emptyel/. It runsgit initunless you pass--no-git-init.--with-dbtadds a dbt project at the root:dbt_project.yml, araw_ordersseed, and staging/marts models with schema tests.To name the project something other than the directory, the flag is
--project-name— there is no--name. -
Create a target. Environment variables alone do not configure a warehouse.
valved initscaffolds the[snowflake.dev]block invalved/connections.tomlcommented out, so a fresh project has no target at all. Do exactly one of:Terminal window valved target create dev…or uncomment the
[snowflake.dev]block invalved/connections.tomlby hand, leaving exactly one of the four auth methods live. Doing both leaves two[snowflake.dev]sections in the file, which is invalid TOML. -
Add your credentials.
Terminal window cp .env.example .env# edit .env: ANTHROPIC_API_KEY + the DEV_SNOWFLAKE_* varsvalved auth status # confirms the harness can see a model credentialOnce a live target exists, its
${DEV_SNOWFLAKE_*}placeholders are interpolated at config load, so everyvalvedcommand — includingconnectandplan— exits withEnvironment variable DEV_SNOWFLAKE_ACCOUNT is not setuntil the whole set is filled in. -
Provision the dbt engine — this is the step that makes the dbt engineer able to run what it writes.
First add the component block by hand —
valved initdoes not write one:valved.toml [components.dbt]type = "dbt"mode = "same-repo"Then provision:
Terminal window valved connect dbtvalved connectresolves an engine for your target’s dialect, pip-installs it into a Valved-managed venv under.valved/engines/(gitignored), validates it, and writes the pin back intovalved.tomlasdbt_engine/dbt_version. Today that always resolves todbt-core1.8.0 plus the matching adapter (dbt-snowflake,dbt-duckdb, …); the Rustfusionengine is pinned correctly but cannot yet be installed. -
Start the control plane (API + scheduler + reaper + archiver + worker pool) in one terminal:
Terminal window valved serve -
Describe what you want. In another terminal, turn intent into a reviewable plan:
Terminal window valved plan "ingest the Stripe charges API into raw_stripe, then a staging model stg_charges"The orchestrator decomposes the goal into sub-goals, routes each to an engineer (DLT, dbt, pipeline, SQL), and runs each in design capacity — read and introspect only, no files written — then merges their designs into one plan.
What prints is a design summary, not a diff: the plan id, pipeline name, description, requirements, the token counts, and the dollar cost of the planning call itself (not an estimate of what the pipeline will cost to run). Destination, strategy, tradeoffs and open questions print only when the design carries those fields — a routed plan’s design uses a different shape, so its summary is usually just name, description, requirements and cost.
The proposed file list is not printed at all. It lands in
.valved/plans/<plan_id>.jsonunderdesign.planned_by_engine, labelled by which engine proposes what; read that file if you want to see what a build would write. There are no file diffs anywhere, because nothing has been written yet.Every plan ends by printing
valved plan --refine <plan_id> "<feedback>"as its suggested next step, and that is the iteration mechanism. A refine of a routed plan stays routed: the same engineers re-run on the same slices, each holding its own prior design plus your feedback, and the child plan comes back with itsplanned_by_engineintact — so refining a dbt goal still gives you a dbt plan. The child keeps the parent’s pipeline name and destination, and links back viaparent_plan_id.This is also the remedy when an engineer ends a plan with a question.
valved buildrefuses the plan, prints the question, and points you atvalved plan --refine <plan_id> "<answer>"; the answer goes to the engine that asked, and when nothing is left unanswered the child plan builds. The refined plan stores the original ask together with your feedback, so the answer is still in front of the engineer at step 7, when it writes the code. -
Build it.
Terminal window valved build <plan_id>Each engineer now runs in build capacity and writes real files: dlt code under
el/<name>/, dbt models and schema files undermodels/. On a routed plan the authored diff then goes through the read-only reviewer fan-out before anyBuildrow is recorded. Nothing is committed.How much “verify by executing” you actually get depends on step 4. The DLT engineer holds gated
bash, and its agent definition tells it not to call the work done until the pipeline runs green. The dbt engineer’sdbt_verifytool is only handed to it when the project has exactly one[components.dbt]block, that block pins an engine, and the pinned engine is on disk. Miss any of those and the tool is simply not granted — no error, no warning — and the dbt engineer authors without ever executing.A convention-discovered dbt project (a
dbt_project.ymlwith no matching[components.*]block) is treated as “no block to carry a pin”, so it never getsdbt_verifyeither. That is exactly the statevalved init --with-dbtleaves you in. -
Run it. Which verb depends on what the build produced:
-
A
pipelines/<name>.tomlexists (you wrote it, or the build edited yours) —valved run <name> --watchenqueues a run and streams its logs. It needsvalved serveorvalved workerrunning to actually execute. -
An extract-load component only —
valved el run <name>runsel/<name>/directly. Note that--watchmeans something different here: it re-runs on filesystem changes rather than streaming a queued run. -
dbt models only — there is no
valved dbtverb, and neithervalved runnorvalved el runhas anything to point at. If step 4 was done, the models are already in your dev target: verifying withdbt_verifymeans running a realdbt buildover the authored selector, which materializes. To run them again on demand or on a schedule, compose them into a pipeline:pipelines/orders.toml [pipeline]description = "Materialize the orders models."[[steps]]id = "dbt"type = "dbt"command = "build"select = "stg_orders+"Omitting
componentmeans “the single detected dbt project”. Thenvalved pipelines validateandvalved run orders.
Iteration is cheap: re-run, adjust, re-run, until the rows look right.
-
-
Deploy it. Promote the built code to version control via a configurable handoff (default: open a PR):
Terminal window valved deploy <pipeline>Valved opens a reviewable pull request (linked PRs across repos when a change spans a graduated component and the control-plane repo). You merge.
--handoff files|commit|push|prstops earlier in that chain.
What just happened
Section titled “What just happened”You walked the core loop: Intent → Plan → Build → Run → Deploy.
To put it on a cadence, set the cron on the live schedule:
valved schedule set-cron <pipeline> "0 6 * * *" --timezone America/Denverset-cron upserts — it creates the schedule if none exists — and takes effect within one scheduler loop interval. A [seed_schedule] block in pipelines/<name>.toml parses and validates, but nothing applies it to the schedules table yet: the reconciler-seed is deferred and valved schedule reseed is a stub that exits non-zero. Set the cron through the CLI. See Scheduling & runs.
Drive it your way
Section titled “Drive it your way”The same loop is available from every surface:
- CLI — the
valvedcommand set (reference) - REST API — the core loop plus the control plane, with auth, streaming and webhooks; project setup stays CLI-only (REST API)
- MCP — from Claude Desktop / Cursor / Claude Code (MCP server)
- Static UI — run history + per-run logs (Static web UI)