agentic-lookml-workflow)flowchart TD
Start["Ask user for missing inputs: SQL Queries + Metrics & Terms Logic + Looker Project (PROJECT_ID)"] --> Preflight["Preflight: scripts/preflight.sh (uv, lkr, gh)"]
Preflight --> Auth["Auth (--dev): lkr auth add-access-token"]
Auth --> InitBranch["Reset to prod & create branch: scripts/init_branch.py -> ./data/:id/data.json"]
InitBranch --> ExtractFiles["Save ./data/:id/input.sql, ./data/:id/metrics.md, initialize summary.md & pull existing LookML"]
ExtractFiles --> ModelCheck{"Existing model or new/no model?"}
ModelCheck -- "No models / Creating new model" --> AskConn["Ask user for CONNECTION_NAME (or offer to list instance connections), default MODEL_NAME=PROJECT_ID, create .model.lkml from examples/template.model.lkml & run scripts/configure_model.py (create_lookml_model)"]
AskConn --> GenBronze
ModelCheck -- "Existing model" --> GenBronze["Generate/refresh Bronze views for all SQL tables: generate_bronze_view.py (log to summary.md)"]
GenBronze --> PullLookML["Pull generated LookML, verify model includes via lkr parse lookml & update baseline snapshots"]
PullLookML --> GenBronzeRefinements["Deconstruct query, identify patterns, author Bronze refinements & verify includes via lkr parse lookml (log to summary.md)"]
GenBronzeRefinements --> PushBronze["Push Bronze to dev branch: lkr --dev tools lookml push --no-reset"]
PushBronze --> PreGate["PRE-MODELING & PRE-TEST GATE: Validate LookML + Init SQL Runner + Early Query Parity"]
PreGate --> PreGateCheck{"Validator or parity errors?"}
PreGateCheck -- "Yes" --> FixBronze["Log 'Validation failed' & 'Updating LookML' to summary.md; self-heal Bronze"]
FixBronze --> PushBronze
PreGateCheck -- "No" --> RunAgentic["Model Silver/Gold & write LookML tests (tests/) in ./data/:id/lookml (log to summary.md)"]
RunAgentic --> SnapshotGate["Check snapshots: lkr parse lookml snapshot --allow-additions -> snapshot_check.json"]
SnapshotGate --> PushFinal["Push final LookML to dev branch: lkr --dev tools lookml push --no-reset"]
PushFinal --> TargetTests["Target new/additional tests via lkr code-mode: run_lookml_test(file_id=..., test=...)"]
TargetTests --> RecordParity["Register final Explore queries: scripts/record_query_parity.py -> /x/:query_id"]
RecordParity --> ValidateCommit["Final validation & targeted tests: scripts/validate_and_commit_branch.py"]
ValidateCommit --> ValidCheck{"Validation or test errors?"}
ValidCheck -- "Yes" --> FixFinal["Log failure & fix to summary.md; self-heal LookML & tests"]
FixFinal --> PushFinal
ValidCheck -- "No" --> OpenPR["Attach summary.md & files, open GitHub PR with query links: scripts/create_github_pr.sh"]
Prerequisite setup sub-skill
If the repository has not been configured yet (or is missing uv, lkr-dev-cli, companion skills in .agents/skills/, an initial .model.lkml file, or .github/workflows/lookml-pr-checks.yml), run the setup sub-skill sub-skills/setup/SKILL.md and scripts/setup_repo.sh first.
Bundled scripts
All workflow helper scripts live in .agents/skills/agentic-lookml-workflow/scripts/ (or skills/agentic-lookml-workflow/scripts/ inside lkrdev/cli):
- setup_repo.sh runs preflight.sh, installs any missing companion skills into
.agents/skills/, installs.github/workflows/lookml-pr-checks.ymlfrom examples/lookml-pr-checks.yml, ensures.gitignoreexcludes.env,data/, andtmp/, and creates an initial<model_name>.model.lkmlwhenMODEL_NAMEandCONNECTION_NAMEare provided. - preflight.sh checks and installs
uvif missing, installslkr-dev-cli[tools,code-mode,parse]viauv tool install, and verifiesgh auth status(passingGH_TOKEN/GITHUB_TOKENdirectly togh). - init_branch.py runs in
lkr code-mode sandbox --dev-modeto fetchme()['id']andgit_production_branch_name, runreset_project_to_production, create branchYYYYMMDD_HHMMSS_mmm_<looker_user_id>from the production ref, and return the basedata.jsonpayload. - configure_model.py runs in
lkr code-mode sandbox --dev-modeto register or update the LookML model configuration on the Looker instance viacreate_lookml_model(orupdate_lookml_model) withallowed_db_connection_names=[connection_name], defaultingmodel_nametoproject_id(with hyphens replaced by underscores) when the project does not have a model yet. Pair with examples/template.model.lkml when creating./data/:id/lookml/<model_name>.model.lkml. - init_sql_queries.py runs in
lkr code-mode sandbox --dev-modeto split multi-statement SQL ininput.sql, create SQL Runner queries viacreate_sql_query(body={"connection_name": connection_name, "sql": stmt})by default (so query creation never fails with400 Model ... has errorsbefore a new model is deployed to production), and populatedata.json["queries"]with0-basedquery_index,sql_query_id, andsql_runner_url({BASE_URL}/sql/{sql_id}). - record_query_parity.py runs in
lkr code-mode sandbox --dev-modeto optionally runvalidate_project(when--var validate="true"is passed), register Looker explore queries viacreate_query(wheninline_queries_jsonis provided, keyed by0-basedquery_index:{"0": {...}}), storequery_id,query_slug, andexplore_url({BASE_URL}/x/{query_slug}), cache baseline SQL Runner rows (sql_rows), and compare row outputs between SQL Runner (run_sql_query) and Looker (run_inline_query/run_query) both locally and in CI. - validate_and_commit_branch.py runs in
lkr code-mode sandbox --dev-modeto validate the project (validate_project), execute targeted LookML data tests (run_lookml_testfiltered bytest_names/file_ids, or skipped viaskip_tests="true"), block commits if any test fails, and optionally commit (commit) and push (push_branch) the branch via the Looker API whencommit_messageis provided. - create_github_pr.sh runs
lkr parse lookml snapshot --allow-additionsifsnapshot_check.jsonis not yet generated, embedssummary.md, the snapshot review status, the interactive SQL Runner (/sql/:sql_id) and Looker Explore (/x/:query_id) link table, and collapsible<details>sections for./data/:id/input.sql,./data/:id/metrics.md,./data/:id/snapshot_check.json, and./data/:id/data.jsondirectly in the PR description, opens the GitHub PR againstgit_production_branch_nameviagh pr create(requesting reviewers when material snapshot changes are detected), posts./data/:id/compare_initial.jsonand./data/:id/compare_post_refactor.jsonin a collapsible<details>PR comment (gh pr comment), and writes the PR URL into./data/:id/data.json.
Workflow steps
- Ask the user directly for any missing inputs on the first prompt: SQL queries (inline or
.sqlfiles), metrics and terms logic (business definitions, common measures, and dimensions/filters used for common analysis), and the target Looker project (PROJECT_ID, plusBASE_URLif not already configured). Do not search the local workspace or query the Looker instance to guess or discover projects unless the user asks to see a list. - Run repository setup and preflight checks (scripts/setup_repo.sh and sub-skills/setup/SKILL.md) to ensure
uv,lkr-dev-cli[tools,code-mode,parse],gh,jq, companion skills, and.github/workflows/lookml-pr-checks.ymlare installed, and authenticate in development mode (--devis mandatory; never run in production):bash .agents/skills/agentic-lookml-workflow/scripts/setup_repo.sh lkr --client-id="$LOOKERSDK_CLIENT_ID" --client-secret="$LOOKERSDK_CLIENT_SECRET" --base-url="$BASE_URL" --dev auth add-access-token - Reset the developer workspace to production, create branch
YYYYMMDD_HHMMSS_mmm_<looker_user_id>off the production ref with scripts/init_branch.py, initializedata.json,input.sql,metrics.md, andsummary.mdinside./data/$RUN_ID/, and pull the project's existing LookML into./data/$RUN_ID/lookml:RAW_BRANCH=$(lkr --base-url="$BASE_URL" --dev code-mode sandbox --dev-mode \ --file=.agents/skills/agentic-lookml-workflow/scripts/init_branch.py \ --var project_id="$PROJECT_ID" \ --var ts_utc="$(date -u +%Y%m%d_%H%M%S_%3N)") BRANCH_JSON=$(echo "$RAW_BRANCH" | sed -n '/^{/,$p') RUN_ID=$(echo "$BRANCH_JSON" | jq -er '.run_id') mkdir -p "./data/$RUN_ID/lookml" echo "$BRANCH_JSON" > "./data/$RUN_ID/data.json" cp "$SQL_FILE" "./data/$RUN_ID/input.sql" cp "$METRICS_FILE" "./data/$RUN_ID/metrics.md" echo "# Agentic LookML operations summary (\`$RUN_ID\`)" > "./data/$RUN_ID/summary.md" lkr --base-url="$BASE_URL" --dev tools lookml pull "./data/$RUN_ID/lookml" --project-id="$PROJECT_ID" - Resolve the target model (
MODEL_NAME) and database connection (CONNECTION_NAME) from./data/$RUN_ID/lookmlbefore generating Bronze views or running initial snapshots (solkr parse lookml snapshot -unever records a syntheticinline.model.lkmlwithconnection: null):- If the project already contains an existing
.model.lkmlfile and you are not creating a new model, readMODEL_NAMEand itsconnection: "$CONNECTION_NAME"declaration from the model file. - If the project does not have any models (
*.model.lkml), or if you are creating a new model, setMODEL_NAME="${PROJECT_ID//-/_}"when no model name exists yet, and always ask the user which connection (CONNECTION_NAME) to use (or ask if they want to display a list of available connections from the Looker instance). After the user providesCONNECTION_NAME, configure the model on the Looker instance viacreate_lookml_modelusing scripts/configure_model.py (create./data/$RUN_ID/lookml/$MODEL_NAME.model.lkmlafter runninggenerate_bronze_view.py+lookml pullsolookml pullnever deletes a newly created local.model.lkmlfile as a local orphan):MODEL_NAME="${MODEL_NAME:-${PROJECT_ID//-/_}}" lkr --base-url="$BASE_URL" --dev code-mode sandbox --dev-mode \ --file=.agents/skills/agentic-lookml-workflow/scripts/configure_model.py \ --var project_id="$PROJECT_ID" \ --var model_name="$MODEL_NAME" \ --var connection_name="$CONNECTION_NAME" - Whenever LookML files are generated or written in
./data/$RUN_ID/lookml, ensure$MODEL_NAME.model.lkmlhasinclude:statements covering the written files (based on examples/template.model.lkml, including only globs that match existing files on disk so Looker's validator does not fail on empty globs), ensure every standalone.explore.lkmland.test.lkmlfile explicitlyinclude:s its referenced.view.lkml,.refinement.lkml, or.explore.lkmlfiles (because Looker's project validator validates standalone.explore.lkmland.test.lkmlfiles in their own scope), and verify withlkr parse lookml --path "./data/$RUN_ID/lookml"thatmodel.<model_name>.view,model.<model_name>.explore, andmodel.<model_name>.testpick up every written view, refinement, explore, and test present infile.view,file.explore, andfile.test.
- If the project already contains an existing
- Throughout the session, append high-level operation milestones to
./data/$RUN_ID/summary.mdas defined in agentic-lookml:- Generating base views (
bronze/:table.view.lkml) - Deconstructing query to canonical form (including the modeling decisions made and, whenever a derived table or Gold explore is used, the specific reason why the query could not be reduced further into a flat Canonical Looker Query)
- Query patterns from original query (for example, single-table with union:
simple_single_table,unions) - Writing LookML (Bronze refinements, hidden base explores, Silver and Gold views and explores)
- Validation failures (
Validation failed: ...) and self-healing updates (Updating LookML: ...) - Writing and targeting LookML tests (
tests/bronze/andtests/silver/)
- Generating base views (
- Run generate-view-from-table (generate_bronze_view.py) for all physical tables in the SQL query every time so every
bronze/:table.view.lkmlis up to date on the branch, pull all LookML into./data/$RUN_ID/lookml, ensure$MODEL_NAME.model.lkmlexists and includes/bronze/**/*.view.lkml, take the baseline snapshot (lkr parse lookml snapshot -u), and log the base view generation in./data/$RUN_ID/summary.md:lkr --base-url="$BASE_URL" --dev code-mode sandbox --dev-mode \ --file=.agents/skills/generate-view-from-table/scripts/generate_bronze_view.py \ --var project_id="$PROJECT_ID" \ --var model_name="$MODEL_NAME" \ --var connection_name="$CONNECTION_NAME" \ --var schema_name="$SCHEMA_NAME" \ --var table_name="$TABLE_NAMES" lkr --base-url="$BASE_URL" --dev tools lookml pull "./data/$RUN_ID/lookml" --project-id="$PROJECT_ID" LOOKML_DIR="./data/$RUN_ID/lookml" MODEL_NAME="$MODEL_NAME" CONNECTION_NAME="$CONNECTION_NAME" \ bash .agents/skills/agentic-lookml-workflow/scripts/setup_repo.sh lkr parse lookml snapshot --path "./data/$RUN_ID/lookml" -u lkr parse sql-lookml-compare --sql-file "./data/$RUN_ID/input.sql" --path "./data/$RUN_ID/lookml" --output "./data/$RUN_ID/compare_initial.json" - Deconstruct the SQL query to canonical form, identify the matched query patterns from the original query, author Bronze refinements (
bronze/:table.refinement.lkml) and hidden base explores (bronze/explores/:table_base.explore.lkml), record these steps in./data/$RUN_ID/summary.md, and push Bronze files to the dev branch:lkr --base-url="$BASE_URL" --dev tools lookml push "./data/$RUN_ID/lookml" --project-id="$PROJECT_ID" --no-reset - Generate the Explore query JSON Schema offline with
lkr parse lookml-to-api --validate(which also fails if duplicate field logic—such as a second unfilteredtype: countmeasure alongsidemeasure: count—exists on any view) andlkr schema generate, and validate yourINLINE_QUERIES_JSON(run_inline_querypayloads) withlkr schema validatebefore calling Looker:lkr parse lookml-to-api --path "./data/$RUN_ID/lookml" -m "$MODEL_NAME" -e "$EXPLORE_NAME" --validate -o "./data/$RUN_ID/explore_${EXPLORE_NAME}.json" lkr schema generate -m "$MODEL_NAME" -e "$EXPLORE_NAME" --explore-file "./data/$RUN_ID/explore_${EXPLORE_NAME}.json" -o "./data/$RUN_ID/schema_${EXPLORE_NAME}.json" lkr schema validate --schema-file "./data/$RUN_ID/schema_${EXPLORE_NAME}.json" --query "$SINGLE_INLINE_QUERY_JSON" - Execute the pre-modeling and pre-test verification gate immediately after Bronze generation and before writing tests or finalizing Silver modeling:
- Initialize SQL Runner queries via
init_sql_queries.pyusing--var connection_name="$CONNECTION_NAME"(defaultingcreate_sql_querytoconnection_namerather thanmodel_name) to capture/sql/:sql_idlinks indata.json:RAW_OUT=$(lkr --base-url="$BASE_URL" --dev code-mode sandbox --dev-mode \ --file=.agents/skills/agentic-lookml-workflow/scripts/init_sql_queries.py \ --var raw_sql="$(cat "./data/$RUN_ID/input.sql")" \ --var data_json="$(cat "./data/$RUN_ID/data.json")" \ --var base_url="$BASE_URL" \ --var connection_name="$CONNECTION_NAME") JSON_OUT=$(echo "$RAW_OUT" | sed -n '/^{/,$p') echo "$JSON_OUT" | jq -e . >/dev/null && echo "$JSON_OUT" > "./data/$RUN_ID/data.json" - Run the LookML validator and early query parity check in a single
code-modesandbox call via scripts/record_query_parity.py with--var validate="true"and0-basedINLINE_QUERIES_JSONkeys ({"0": {...}}matchingquery_index: 0) before writing any tests, logging anyValidation failedandUpdating LookMLself-healing steps to./data/$RUN_ID/summary.md:RAW_OUT=$(lkr --base-url="$BASE_URL" --dev code-mode sandbox --dev-mode \ --file=.agents/skills/agentic-lookml-workflow/scripts/record_query_parity.py \ --var data_json="$(cat "./data/$RUN_ID/data.json")" \ --var base_url="$BASE_URL" \ --var validate="true" \ --var inline_queries_json="$INLINE_QUERIES_JSON") JSON_OUT=$(echo "$RAW_OUT" | sed -n '/^{/,$p') echo "$JSON_OUT" | jq -e . >/dev/null && echo "$JSON_OUT" > "./data/$RUN_ID/data.json"
- Initialize SQL Runner queries via
- Model Silver explores, cross-view
*_fields, and author LookML data tests (tests/bronze/andtests/silver/) in./data/$RUN_ID/lookmlfollowing agentic-lookml (never creating a duplicate field with identical logic on the same view, such as a secondtype: countmeasure whenmeasure: count { type: count }already exists; refinemeasure: countinstead, and use the Explore JSON Schema fromlkr schema generateto verify validview.fieldreferences and filter expressions forexplore_source:), log the authored LookML and tests in./data/$RUN_ID/summary.md, re-run compare, and evaluate LookML snapshots with--allow-additionsso pure additions pass automatically while any modified or removed test assert, test field, connection name, measure label, access grant, or manifest setting is flagged insnapshot_check.json:lkr parse lookml-to-api --path "./data/$RUN_ID/lookml" -m "$MODEL_NAME" -e "$EXPLORE_NAME" --validate -o "./data/$RUN_ID/explore_${EXPLORE_NAME}.json" lkr schema generate -m "$MODEL_NAME" -e "$EXPLORE_NAME" --explore-file "./data/$RUN_ID/explore_${EXPLORE_NAME}.json" -o "./data/$RUN_ID/schema_${EXPLORE_NAME}.json" lkr parse sql-lookml-compare --sql-file "./data/$RUN_ID/input.sql" --path "./data/$RUN_ID/lookml" --output "./data/$RUN_ID/compare_post_refactor.json" lkr parse lookml snapshot --path "./data/$RUN_ID/lookml" --allow-additions --format json --output "./data/$RUN_ID/snapshot_check.json" || true lkr parse lookml snapshot --path "./data/$RUN_ID/lookml" -u - Push final LookML edits (including
__snapshots__/*.snap.strings.json) to the active developer branch with--no-reset:lkr --base-url="$BASE_URL" --dev tools lookml push "./data/$RUN_ID/lookml" --project-id="$PROJECT_ID" --no-reset - Register final Looker explore queries in development mode, capture
/x/:query_idshort URLs, and record final parity status:RAW_OUT=$(lkr --base-url="$BASE_URL" --dev code-mode sandbox --dev-mode \ --file=.agents/skills/agentic-lookml-workflow/scripts/record_query_parity.py \ --var data_json="$(cat "./data/$RUN_ID/data.json")" \ --var base_url="$BASE_URL" \ --var inline_queries_json="$INLINE_QUERIES_JSON") JSON_OUT=$(echo "$RAW_OUT" | sed -n '/^{/,$p') echo "$JSON_OUT" | jq -e . >/dev/null && echo "$JSON_OUT" > "./data/$RUN_ID/data.json" - Target every new or additional test with
lkr code-mode(run_lookml_testfiltered byfile_idsandtest_names), validate syntax, commit, and push the branch tooriginvia scripts/validate_and_commit_branch.py (self-healing and logging any validation or test errors in./data/$RUN_ID/summary.mdbefore committing), and run scripts/create_github_pr.sh to embed./data/$RUN_ID/summary.md, interactive query links, and session artifacts in the PR description and open the GitHub PR:lkr --base-url="$BASE_URL" --dev code-mode sandbox --dev-mode \ --file=.agents/skills/agentic-lookml-workflow/scripts/validate_and_commit_branch.py \ --var project_id="$PROJECT_ID" \ --var file_ids="$NEW_TEST_FILE_IDS" \ --var test_names="$NEW_TEST_NAMES" \ --var commit_message="feat(lookml): refactor SQL queries ($RUN_ID)" bash .agents/skills/agentic-lookml-workflow/scripts/create_github_pr.sh "./data/$RUN_ID"
Example GitHub PR checks workflow (examples/lookml-pr-checks.yml)
Customers can copy examples/lookml-pr-checks.yml into .github/workflows/lookml-pr-checks.yml in their LookML repository. When pull requests containing LookML changes, snapshots, or ./data/*/data.json session files are created, updated, or reviewed, GitHub Actions runs three sequential steps in a single job:
- The first step runs
lkr parse lookml snapshot --path "$LOOKML_DIR" --base-path "$BASE_LOOKML_DIR" --allow-additions --format jsonagainst the base branch (origin/${{ github.base_ref }}). Purely additive changes (adding new tests, new assertions, new test columns or derived columns, new measures, or adding labels to previously unlabeled measures) pass automatically withrequires_review: false. Material changes (modifying or removing an existing test assertion, modifying or removing an existing test field, changing a database connection name, changing or removing a label on an existing measure, or modifying/removing access controls or manifest entries) setrequires_review: true, resolve the affected.lkmland.snap.jsonfiles (material_change_files) againstCODEOWNERS(required_reviewers), post a summary of the material changes and required Code Owners, request reviews from the matchedCODEOWNERS(plusLOOKML_REVIEWERSif configured), and block the check until the required Code Owners approve the PR. - The second step runs scripts/validate_and_commit_branch.py (without
commit_message) vialkr --base-url="$LOOKERSDK_BASE_URL" --dev code-mode sandbox --dev-modeto execute Looker'svalidate_projectandrun_lookml_teston the branch, failing immediately if syntax errors, broken references, or LookML data tests fail. - The third step runs only after the validator step succeeds, executing scripts/record_query_parity.py (without
inline_queries_json) vialkr --base-url="$LOOKERSDK_BASE_URL" --dev code-mode sandbox --dev-modeto compare each SQL Runner query (/sql/:sql_id) against its corresponding Looker explore query (/x/:query_id) and verify numerical parity.