Why Manage Instructions as Code?
Your analytics knowledge—business definitions, calculation formulas, SQL patterns—shouldn’t live in a UI or people’s heads. Treating instructions as code brings the same rigor you use for data pipelines:
- Version control: Full history of what changed, when, and why
- Code review: Team members review instruction changes before they go live
- Automated testing: Run evals on every PR to catch regressions
- Single source of truth: Documentation stays in sync with your data models
- Collaboration: Data engineers, analysts, and AI work from the same definitions
If you already manage dbt models, LookML, or documentation in Git, Bow fits right into that workflow. Your existing docs become instructions automatically.
Connecting a Repository
Connect your Git repository in Data Sources. Bow supports:
| Source | Default Load Mode |
|---|
| Markdown files (.md) | Always |
| dbt models, metrics, sources | Intelligent |
| dbt seeds | Intelligent |
| dbt macros, tests | Disabled |
| LookML views, models, explores | Intelligent |
Repository Settings
| Setting | Description |
|---|
| Branch | Which branch to sync from |
| Auto Publish | Automatically approve synced instructions |
| Default Load Mode | Auto, Always, Intelligent, or Disabled |
Writing Instructions in Markdown
Create .md files in your repository with YAML frontmatter to control how Bow loads them.
Basic Example
---
alwaysApply: true
references:
- customers
- orders
---
# Customer Lifetime Value
CLV = SUM(order_total) WHERE customer_id = X AND status = 'completed'
Use 12-month lookback window from analysis date.
Frontmatter Options
| Field | Type | Description |
|---|
alwaysApply | boolean | true → Load mode = Always, false → Intelligent |
references | list | Tables/views this instruction references |
status | string | published (default), draft, archived |
category | string | general, code, data_modeling, visualizations |
Git CI/CD Integration
Automate your instruction workflow: create instructions in Git, run evals on PR, and deploy on merge.
Prerequisites
- Git Repository connected to Bow with a Personal Access Token (PAT)
- API Key from Bow Settings → API Keys
- Test Suite with test cases configured in Bow
API Reference
| Endpoint | Method | Description |
|---|
/git/{repo_id}/sync | POST | Sync a branch → creates DRAFT build |
/tests/runs/batch | POST | Run evals against a build |
/tests/runs/{run_id}/status | GET | Get eval results |
/builds/{build_id}/publish | POST | Publish build to main |
Base API URL should be with /api suffix. i.e https://app.bagofwords.com/api
GitHub Actions Setup
1. Add Repository Secrets
| Secret | Description |
|---|
BOW_API_KEY | Your Bow API key |
BOW_REPO_ID | Git repository ID from Bow |
BOW_SUITE_ID | Test suite ID for evals |
2. Add Repository Variable
| Variable | Description |
|---|
BOW_API_URL | Your Bow instance URL (e.g., https://app.bagofwords.com/api) |
3. Create Workflow File
Create .github/workflows/bow.yml:
name: Bow CI/CD
on:
pull_request:
paths:
- '**/*.md'
- 'bow/**'
push:
branches: [main]
paths:
- '**/*.md'
- 'bow/**'
env:
BOW_API_URL: ${{ vars.BOW_API_URL }}
BOW_REPO_ID: ${{ secrets.BOW_REPO_ID }}
BOW_API_KEY: ${{ secrets.BOW_API_KEY }}
jobs:
# ============================================
# On Pull Request: Sync + Run Evals
# ============================================
test:
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
steps:
- name: Sync branch to Bow
id: sync
run: |
echo "Syncing branch '${{ github.head_ref }}' to Bow..."
RESPONSE=$(curl -sf -X POST "$BOW_API_URL/git/$BOW_REPO_ID/sync" \
-H "Authorization: Bearer $BOW_API_KEY" \
-H "Content-Type: application/json" \
-d '{"branch": "${{ github.head_ref }}"}')
BUILD_ID=$(echo $RESPONSE | jq -r '.build_id')
BUILD_NUM=$(echo $RESPONSE | jq -r '.build_number')
echo "build_id=$BUILD_ID" >> $GITHUB_OUTPUT
echo "✅ Created draft build #$BUILD_NUM"
- name: Run evals
id: evals
run: |
echo "Starting evals against build ${{ steps.sync.outputs.build_id }}..."
# Start test run
RUN_RESPONSE=$(curl -sf -X POST "$BOW_API_URL/tests/runs/batch" \
-H "Authorization: Bearer $BOW_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"suite_id": "${{ secrets.BOW_SUITE_ID }}",
"build_id": "${{ steps.sync.outputs.build_id }}",
"trigger_reason": "github_pr"
}')
RUN_ID=$(echo $RUN_RESPONSE | jq -r '.id')
echo "run_id=$RUN_ID" >> $GITHUB_OUTPUT
# Poll for completion
echo "Waiting for evals to complete..."
while true; do
STATUS_RESPONSE=$(curl -sf "$BOW_API_URL/tests/runs/$RUN_ID/status" \
-H "Authorization: Bearer $BOW_API_KEY")
STATUS=$(echo $STATUS_RESPONSE | jq -r '.run.status')
if [[ "$STATUS" == "completed" || "$STATUS" == "failed" ]]; then
PASSED=$(echo $STATUS_RESPONSE | jq -r '.run.summary_json.passed // 0')
FAILED=$(echo $STATUS_RESPONSE | jq -r '.run.summary_json.failed // 0')
echo "passed=$PASSED" >> $GITHUB_OUTPUT
echo "failed=$FAILED" >> $GITHUB_OUTPUT
break
fi
sleep 10
done
- name: Post results to PR
uses: actions/github-script@v7
with:
script: |
const passed = '${{ steps.evals.outputs.passed }}';
const failed = '${{ steps.evals.outputs.failed }}';
const buildId = '${{ steps.sync.outputs.build_id }}';
const status = failed > 0 ? '❌' : '✅';
github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: `## ${status} Bow Eval Results\n\n| Passed | Failed |\n|--------|--------|\n| ${passed} | ${failed} |\n\n[View in Bow](${process.env.BOW_API_URL}/evals?build=${buildId})`
});
- name: Fail if evals failed
if: steps.evals.outputs.failed > 0
run: exit 1
# ============================================
# On Merge to Main: Publish Build
# ============================================
publish:
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- name: Get build for merged branch
id: get_build
run: |
# Get the most recent draft build from git source
RESPONSE=$(curl -sf "$BOW_API_URL/builds?status=draft&limit=1" \
-H "Authorization: Bearer $BOW_API_KEY")
BUILD_ID=$(echo $RESPONSE | jq -r '.items[0].id')
if [[ "$BUILD_ID" == "null" || -z "$BUILD_ID" ]]; then
echo "No draft build found to publish"
exit 0
fi
echo "build_id=$BUILD_ID" >> $GITHUB_OUTPUT
- name: Publish build
if: steps.get_build.outputs.build_id
run: |
echo "Publishing build ${{ steps.get_build.outputs.build_id }}..."
curl -sf -X POST "$BOW_API_URL/builds/${{ steps.get_build.outputs.build_id }}/publish" \
-H "Authorization: Bearer $BOW_API_KEY"
echo "✅ Build published to main"
How It Works
On PR Open/Update
- Sync:
POST /git/{repo_id}/sync pulls your branch and creates a draft build
- Eval:
POST /tests/runs/batch runs your test suite against the draft build
- Report: Results are posted as a PR comment
On PR Merge
- Publish:
POST /builds/{build_id}/publish promotes the draft build to main
- Your instructions are now live in Bow
Unlinking from Git
If you need to modify a Git-synced instruction directly in Bow:
- Edit the instruction in Bow
- Save changes
- Confirm “Unlink from Git” when prompted
The instruction becomes user-owned and will no longer sync from the repository.