Skip to content

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.

  1. Scaffold a project with a dbt project and a bundled Postgres:

    Terminal window
    mkdir my-valved-project && cd my-valved-project
    valved init --with-dbt
    docker compose up -d # start the state store

    valved init writes valved.toml, the valved/ config dir (connections.toml, models.toml, runner.toml, and the memory files), .env.example, .gitignore, docker-compose.yml, and an empty el/. It runs git init unless you pass --no-git-init. --with-dbt adds a dbt project at the root: dbt_project.yml, a raw_orders seed, 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.

  2. Create a target. Environment variables alone do not configure a warehouse. valved init scaffolds the [snowflake.dev] block in valved/connections.toml commented 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 in valved/connections.toml by hand, leaving exactly one of the four auth methods live. Doing both leaves two [snowflake.dev] sections in the file, which is invalid TOML.

  3. Add your credentials.

    Terminal window
    cp .env.example .env
    # edit .env: ANTHROPIC_API_KEY + the DEV_SNOWFLAKE_* vars
    valved auth status # confirms the harness can see a model credential

    Once a live target exists, its ${DEV_SNOWFLAKE_*} placeholders are interpolated at config load, so every valved command — including connect and plan — exits with Environment variable DEV_SNOWFLAKE_ACCOUNT is not set until the whole set is filled in.

  4. 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 init does not write one:

    valved.toml
    [components.dbt]
    type = "dbt"
    mode = "same-repo"

    Then provision:

    Terminal window
    valved connect dbt

    valved connect resolves 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 into valved.toml as dbt_engine / dbt_version. Today that always resolves to dbt-core 1.8.0 plus the matching adapter (dbt-snowflake, dbt-duckdb, …); the Rust fusion engine is pinned correctly but cannot yet be installed.

  5. Start the control plane (API + scheduler + reaper + archiver + worker pool) in one terminal:

    Terminal window
    valved serve
  6. 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>.json under design.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 its planned_by_engine intact — so refining a dbt goal still gives you a dbt plan. The child keeps the parent’s pipeline name and destination, and links back via parent_plan_id.

    This is also the remedy when an engineer ends a plan with a question. valved build refuses the plan, prints the question, and points you at valved 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.

  7. 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 under models/. On a routed plan the authored diff then goes through the read-only reviewer fan-out before any Build row 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’s dbt_verify tool 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.yml with no matching [components.*] block) is treated as “no block to carry a pin”, so it never gets dbt_verify either. That is exactly the state valved init --with-dbt leaves you in.

  8. Run it. Which verb depends on what the build produced:

    • A pipelines/<name>.toml exists (you wrote it, or the build edited yours) — valved run <name> --watch enqueues a run and streams its logs. It needs valved serve or valved worker running to actually execute.

    • An extract-load component onlyvalved el run <name> runs el/<name>/ directly. Note that --watch means something different here: it re-runs on filesystem changes rather than streaming a queued run.

    • dbt models only — there is no valved dbt verb, and neither valved run nor valved el run has anything to point at. If step 4 was done, the models are already in your dev target: verifying with dbt_verify means running a real dbt build over 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 component means “the single detected dbt project”. Then valved pipelines validate and valved run orders.

    Iteration is cheap: re-run, adjust, re-run, until the rows look right.

  9. 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|pr stops earlier in that chain.

You walked the core loop: Intent → Plan → Build → Run → Deploy.

To put it on a cadence, set the cron on the live schedule:

Terminal window
valved schedule set-cron <pipeline> "0 6 * * *" --timezone America/Denver

set-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.

The same loop is available from every surface:

  • CLI — the valved command 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)