Skip to content

Reference

Runbook YAML schema.

A runbook YAML is the declarative form of a long briar extract command line. Run it once with briar runbook extract, schedule it with briar runbook serve, or batch over a directory with briar runbook sweep. One file can describe many companies, each with many schedules, each running many extractors.

On this page

Minimal example

The smallest useful runbook — one company, one extractor, no schedule.

version: 1
companies:
acme:
knowledge:
store: file
name: knowledge:acme
extract:
- name: pr-archaeology
args:
pr_repo: [acme/widgets]
- name: active-tickets
args:
tracker: jira
ticket_project: KAN

Run it with briar runbook extract acme.yaml. Both extractors fire once; the result is written to ./knowledge/knowledge/acme.md.

Full-shape example

Every block populated. Use as a template and trim down to what you actually need.

version: 1
companies:
acme:
profile: production
knowledge:
store: postgres
name: knowledge:acme
config:
dsn_env: BRIAR_ACME_DATABASE_URL
git_identity:
name: Briar Agent
messages:
ticket_comment: { kind: jira-comment }
ops_chat: { kind: slack-channel }
escalation:
kind: telegram-chat
config:
chat_env: TELEGRAM_ACME_ALT_CHAT
extract:
- name: pr-archaeology
args:
provider: github
pr_repo: [acme/widgets, acme/api]
pr_max: 100
- name: aws-infra
args:
cloud: aws
aws_extract_region: us-east-1
aws_extract_service: [ecs, lambda, rds]
schedules:
- task: extractors
every: "day at 07:30"
extract:
- name: pr-archaeology
- name: meeting-digest
- task: hourly-tickets
every: "1 hour"
extract:
- name: active-tickets

Top-level keys

version INTdefault: 1
Schema version. Only 1 ships today.
companies MAP
Map of company key CompanyEntry. The company key is the token used in env-var interpolation — --company acme matches companies.acme and GITHUB_ACME_TOKEN.

CompanyEntry

profile STRING
Free-form. Useful as a "production"/"staging" marker.
workspace_id STRING
Optional workspace identifier (passed through to providers that need it).
knowledge_file PATH
Legacy field. New configs use the structured knowledge: block below.
knowledge KnowledgeBinding
Where the company knowledge blob lands. See KnowledgeBinding below.
extract LIST[ExtractEntry]
Per-company default extractor list. Run by briar runbook extract when no --task is passed.
schedules LIST[ScheduleEntry]
Named groups of extractors with a cadence. Consumed by briar runbook serve.
messages MAP[name, MessageBinding]
Named outbound-message channels. briar agent's send_message tool resolves names to writers at runtime.
git_identity GitIdentity
Per-company commit author for briar agent worktree commits.

ExtractEntry

name EXTRACTORrequired
Extractor name. Must be a key in briar.extract.EXTRACTORS — see the registries page.
args MAP
Free-form arg map. Keys match the CLI flag names with - replaced by _ (e.g. pr-repopr_repo). See the mapping table below.

ScheduleEntry

task STRINGrequired
Schedule name. Used by briar runbook extract --task to fire one schedule on demand.
every STRINGrequired
Cadence expression parsed by EveryParser. See the full grammar in the cadence DSL section.
extract LIST[ExtractEntry]
Extractors that fire on this cadence.

KnowledgeBinding

store {file,postgres}default: file
filepostgres
Knowledge-store backend.
name BLOB_NAME
Blob name. Empty = "not configured" — callers fall back to knowledge:<company>.
root PATH
File-store root override.
config MAP
Backend-specific config. The postgres backend reads dsn_env (the env-var name holding the DSN — never the DSN itself). Set inventory: "true" to also write the JSON inventory companion (below).

config.inventory — full detail on demand

With config.inventory: "true", every scheduled run also writes a stable JSON companion blob (knowledge:acme inventory:acme, category inventory) holding the full structured data each extractor produced — the detail the prompt-baked markdown drops (e.g. every tagged AWS resource from tagging-inventory). It's byte-stable, so it only rewrites on real drift; the history table becomes an estate change log. Inspect with briar context list --prefix inventory:. Off by default.

Postgres DSN resolution order

  • config.dsn_env (if set in YAML) — explicit per-binding override
  • BRIAR_{COMPANY}_DATABASE_URL — per-company env var
  • BRIAR_DATABASE_URL — global fallback
First non-empty wins. Lets two companies on one host point at different databases.

MessageBinding

kind WRITERrequired
Writer kind. Must be a key in briar.messaging.WRITERS: jira-comment, jira-transition, github-pr-comment, bitbucket-pr-comment, slack-channel, telegram-chat.
config MAP
Per-binding config (channel overrides, env-var aliases, default statuses). Each writer decides which keys it understands — see per-writer config keys below.

GitIdentity

name STRING
git config user.name for briar agent commits.
email STRING
git config user.email.

CLI overrides win

If both the YAML git_identity and the CLI flags (--git-user-name / --git-user-email) are set, the CLI flag wins. Each field resolves independently — you can set name in YAML and override only email from the CLI.

The every: cadence DSL

EveryParser turns the YAML's tiny DSL into a schedule.Job instance. Three production rules cover everything.

Grammar

<interval> "minute" | "hour" | "day" | "week" | <weekday>
<n> <interval> "10 minutes" | "4 hours" | "2 days" | "3 weeks"
<interval> at <HH:MM> "day at 03:17" | "monday at 09:00" | "hour at :15"
<interval> ::= "minute" | "hour" | "day" | "week"
<weekday> ::= "monday" | "tuesday" | "wednesday" | "thursday"
| "friday" | "saturday" | "sunday"
<n> ::= positive integer (no count allowed on weekdays)

Cadence examples

Cadence stringFires
"minute"Every minute (use for testing only — most extractors take longer than a minute).
"5 minutes"Every 5 minutes.
"hour"Every hour, at a deterministic stagger offset (see callout).
"hour at :15"Every hour at HH:15.
"4 hours"Every 4 hours.
"12 hours"Every 12 hours.
"day"Once per day, at the scheduler's start hour.
"day at 03:17"Daily at 03:17 wall-clock.
"day at 07:30"Daily at 07:30 — common for nightly knowledge refresh.
"monday at 09:00"Every Monday at 09:00. Weekdays do not accept a count.
"2 weeks"Every 2 weeks at the scheduler's start time.

Deterministic staggering

For sub-day cadences (minute, hour, N minutes, N hours) the scheduler computes a stable offset from the SHA-1 of <company>:<task> modulo the cadence. A 1-hour job staggers 0–59 minutes, a 4-hour job staggers 0–239 minutes. Restarts land on the same offset — no thundering-herd at process start.

How args: map to CLI flags

Anything you can pass to briar extract on the command line, you can put under args: in YAML. Two rules:

  • Replace - with _ in flag names. --pr-repo pr_repo; --aws-extract-region aws_extract_region.
  • Repeatable flags become YAML lists. --pr-repo a --pr-repo b pr_repo: [a, b].
CLI flagYAML key
--pr-repopr_repo (list)
--pr-maxpr_max (int)
--pr-authors-allowpr_authors_allow (list)
--pr-authors-blockpr_authors_block (list)
--providerprovider
--trackertracker
--cloudcloud
--aws-extract-regionaws_extract_region
--aws-extract-profileaws_extract_profile
--aws-extract-serviceaws_extract_service (list)
--ticket-projectticket_project (list)
--ticket-maxticket_max (int)
--ticket-archaeology-projectticket_archaeology_project (list)
--active-repoactive_repo (list)
--active-authors-allowactive_authors_allow (list)
--active-authors-blockactive_authors_block (list)
--deploy-repodeploy_repo (list)
--conventions-repoconventions_repo (list)
--reviewer-reporeviewer_repo (list)
--reviewer-pr-samplereviewer_pr_sample (int)
--reviewer-top-nreviewer_top_n (int)
--hotspots-repohotspots_repo (list)
--hotspots-since-dayshotspots_since_days (int)
--hotspots-max-commitshotspots_max_commits (int)
--hotspots-top-nhotspots_top_n (int)
--meetingmeeting
--meeting-since-daysmeeting_since_days (int)
--meeting-maxmeeting_max (int)
--meeting-attendee-allowmeeting_attendee_allow (list)

Per-writer config: keys

Each writer kind decides what keys its config: map understands. Three of the six ship config-aware behaviour today.

WriterConfig keyEffect
jira-commentNo config keys. Target is the ticket key; body is the comment.
jira-transitionstatusDefault transition target (e.g. "Done", "In Review"). Overridable per send via extras.status.
github-pr-commentNo config. Target is owner/repo#pr or owner/repo + extras.pr.
bitbucket-pr-commentSame shape as the GitHub writer.
slack-channelwebhook_envOverride the env-var name holding the webhook URL. Default reads SLACK_{COMPANY}_WEBHOOK_URL. Lets a company have multiple named Slack bindings (ops, escalation) pointing at different channels.
telegram-chatchat_envOverride the env-var name holding the chat ID. Default: TELEGRAM_{COMPANY}_CHAT_ID.

More examples

Each example below is a complete, valid runbook. Copy, edit the company key, drop in real repos / project keys, and you have something runnable.

Single GitHub repo, no schedule

Cold extract a single repo on demand. No scheduler involved.

version: 1
companies:
acme:
knowledge:
store: file
name: knowledge:acme
extract:
- name: pr-archaeology
args:
pr_repo: [acme/widgets]
pr_max: 50
pr_authors_block: ["renovate[bot]", "dependabot[bot]"]
- name: code-hotspots
args:
hotspots_repo: [acme/widgets]
hotspots_since_days: 30
hotspots_top_n: 15
- name: codebase-conventions
args:
conventions_repo: [acme/widgets]
$ briar runbook extract acme.yaml

Multi-source (GitHub + Jira + AWS) with daily schedule

The mainstream stack: code in GitHub, tickets in Jira, infra in AWS. Schedule fires once a day at 03:17.

version: 1
companies:
acme:
knowledge:
store: file
name: knowledge:acme
schedules:
- task: nightly
every: "day at 03:17"
extract:
- name: pr-archaeology
args:
pr_repo: [acme/widgets, acme/api, acme/shared]
pr_max: 100
pr_authors_allow: [alice, bob, carol]
- name: active-tickets
args:
tracker: jira
ticket_project: [ACME, INFRA]
- name: ticket-archaeology
args:
tracker: jira
ticket_archaeology_project: [ACME]
ticket_max: 50
- name: aws-infra
args:
cloud: aws
aws_extract_region: us-east-1
aws_extract_service: [ecs, lambda, rds, sqs, logs]
- name: reviewer-profile
args:
reviewer_repo: [acme/widgets, acme/api]
reviewer_top_n: 5
- name: code-hotspots
args:
hotspots_repo: [acme/widgets, acme/api]
hotspots_since_days: 30

Multi-company file

Three companies in one file, each on a different cadence. briar runbook serve registers all three with deterministic staggering.

version: 1
companies:
acme:
knowledge: { store: postgres, name: knowledge:acme }
schedules:
- task: extractors
every: "day at 07:00"
extract:
- name: pr-archaeology
args: { pr_repo: [acme/widgets] }
- name: active-tickets
args: { tracker: jira, ticket_project: [ACME] }
widgets:
knowledge: { store: postgres, name: knowledge:widgets }
schedules:
- task: extractors
every: "day at 07:30"
extract:
- name: pr-archaeology
args: { pr_repo: [widgets/core] }
- name: code-hotspots
args: { hotspots_repo: [widgets/core], hotspots_since_days: 14 }
zenith:
knowledge: { store: postgres, name: knowledge:zenith }
schedules:
- task: hourly-active
every: "1 hour"
extract:
- name: active-work
args: { active_repo: [zenith/platform] }

Bitbucket Cloud + Linear (full vendor swap)

Same extractor names, different provider implementations. Knowledge goes to Postgres so the scheduler + dashboard share state without file-lock contention.

version: 1
companies:
bitspark:
knowledge:
store: postgres
name: knowledge:bitspark
config:
dsn_env: BRIAR_BITSPARK_KB_PG
messages:
pr_reply: { kind: bitbucket-pr-comment }
ops_chat: { kind: slack-channel }
escalation: { kind: telegram-chat }
schedules:
- task: archaeology
every: "monday at 02:30"
extract:
- name: pr-archaeology
args:
provider: bitbucket
pr_repo: [bitspark/platform, bitspark/api]
pr_max: 50
- name: ticket-archaeology
args:
tracker: linear
ticket_archaeology_project: [ENG]
- task: prfix
every: "hour"
extract:
- name: active-work
args:
provider: bitbucket
active_repo: [bitspark/platform]
- name: active-tickets
args:
tracker: linear
ticket_project: [ENG]
- task: extractors
every: "day at 04:17"
extract:
- name: aws-infra
args:
cloud: aws
aws_extract_region: us-west-2

GCP cloud (non-AWS provider)

The extractor is still called aws-infra (kept for back-compat) — cloud: gcp routes it onto the GCP provider. The flag names read AWS-flavoured but carry the GCP project ID / region.

version: 1
companies:
datacore:
knowledge:
store: file
name: ./knowledge/datacore/main.md
schedules:
- task: extractors
every: "day at 05:00"
extract:
- name: aws-infra
args:
cloud: gcp
aws_extract_profile: datacore-prod # GCP project ID
aws_extract_region: us-central1

Azure cloud

version: 1
companies:
fintech:
knowledge: { store: postgres, name: knowledge:fintech }
schedules:
- task: extractors
every: "day at 07:30"
extract:
- name: aws-infra
args:
cloud: azure
aws_extract_profile: 11111111-2222-3333-4444-555555555555 # subscription ID
aws_extract_region: eastus

Meeting digest with attendee filter

Only include meetings with at least one allowed attendee. meeting_attendee_allow is repeatable and folds into an OR filter.

version: 1
companies:
acme:
knowledge: { store: file, name: knowledge:acme }
schedules:
- task: meetings
every: "hour"
extract:
- name: meeting-digest
args:
meeting: fireflies
meeting_since_days: 7
meeting_max: 25
meeting_attendee_allow:

Many cadences in one company

Real-world setup: nightly heavyweights, hourly tickets, 4-hourly implementation context, weekly archaeology.

version: 1
companies:
acme:
knowledge: { store: postgres, name: knowledge:acme }
schedules:
# Nightly heavyweights — slow but complete.
- task: nightly
every: "day at 03:17"
extract:
- name: pr-archaeology
args: { pr_repo: [acme/widgets, acme/api] }
- name: reviewer-profile
args: { reviewer_repo: [acme/widgets], reviewer_top_n: 5 }
- name: code-hotspots
args: { hotspots_repo: [acme/widgets], hotspots_since_days: 30 }
# Implementation context, refreshed often.
- task: implementation
every: "4 hours"
extract:
- name: codebase-conventions
args: { conventions_repo: [acme/widgets, acme/api] }
- name: github-deployments
args: { deploy_repo: [acme/widgets] }
# Tickets, hourly.
- task: tickets
every: "hour"
extract:
- name: active-tickets
args: { tracker: jira, ticket_project: [ACME] }
# PR-fix context, hourly.
- task: prfix
every: "hour"
extract:
- name: active-work
args:
active_repo: [acme/widgets, acme/api]
active_authors_block: ["renovate[bot]", "dependabot[bot]"]
# Weekly deep-dive.
- task: weekly-archaeology
every: "monday at 02:00"
extract:
- name: ticket-archaeology
args:
tracker: jira
ticket_archaeology_project: [ACME]
ticket_max: 200

Messages — every writer kind

Wire all six writer kinds. The agent's send_message tool resolves the channel name to a writer at runtime.

version: 1
companies:
acme:
knowledge: { store: postgres, name: knowledge:acme }
messages:
# Jira write paths — distinct verbs, same auth.
ticket_comment:
kind: jira-comment
ticket_transition:
kind: jira-transition
config:
status: "In Review" # default; agent can override via extras.status
# GitHub PR comment (top-level or inline review reply).
pr_reply:
kind: github-pr-comment
# Bitbucket equivalent.
bb_pr_reply:
kind: bitbucket-pr-comment
# Slack — default channel via SLACK_ACME_WEBHOOK_URL.
ops_chat:
kind: slack-channel
# Slack — second channel via per-binding env-var override.
escalation_slack:
kind: slack-channel
config:
webhook_env: SLACK_ACME_ESCALATION_WEBHOOK_URL
# Telegram — alt-channel override.
escalation_telegram:
kind: telegram-chat
config:
chat_env: TELEGRAM_ACME_ALT_CHAT

Jira session-cookie auth (SSO tenants)

Some Atlassian tenants disable API tokens and require session-cookie auth. Set the session env vars and Briar auto-detects which auth mode to use.

version: 1
companies:
acme:
knowledge: { store: file, name: knowledge:acme }
schedules:
- task: tickets
every: "hour"
extract:
- name: active-tickets
args:
tracker: jira
ticket_project: [ACME]
# Env vars (set out-of-band, e.g. in secrets.env):
# JIRA_ACME_URL = https://acme.atlassian.net
# JIRA_ACME_TENANT_SESSION_TOKEN = <tenant.session.token cookie value>
# JIRA_ACME_XSRF_TOKEN = <atlassian.xsrf.token cookie value>
#
# Optional, force the auth picker:
# JIRA_ACME_AUTH_KIND=session

Staging vs production via profile

Use the free-form profile: field as a marker, and put staging on a slower cadence with a separate knowledge blob.

version: 1
companies:
acme-prod:
profile: production
knowledge: { store: postgres, name: knowledge:acme-prod }
schedules:
- task: extractors
every: "day at 03:17"
extract:
- name: pr-archaeology
args: { pr_repo: [acme/widgets] }
acme-staging:
profile: staging
knowledge: { store: postgres, name: knowledge:acme-staging }
schedules:
- task: extractors
every: "day at 04:00"
extract:
- name: pr-archaeology
args: { pr_repo: [acme/widgets], pr_max: 25 }

Cookbook patterns

Share extractor args via anchors

YAML anchors and aliases let you avoid repeating long arg blocks across schedules.

version: 1
companies:
acme:
knowledge: { store: file, name: knowledge:acme }
schedules:
- task: nightly
every: "day at 03:17"
extract:
- name: pr-archaeology
args: &pr_args
pr_repo: [acme/widgets, acme/api]
pr_max: 100
pr_authors_block: ["renovate[bot]", "dependabot[bot]"]
- task: backfill
every: "monday at 02:00"
extract:
- name: pr-archaeology
args:
<<: *pr_args
pr_max: 500 # override just this key

Run one task on demand

Pass --task to briar runbook extract to fire one named schedule without waiting for its cadence.

$ briar runbook extract runbooks/acme.yaml --task tickets
$ briar runbook extract runbooks/acme.yaml --task nightly

Avoid thundering-herd at startup

You don't need to. Briar staggers sub-day jobs deterministically using SHA-1 of <company>:<task>. A 1-hour job for acme:tickets and widgets:tickets land at different minute offsets. Restarts hit the same offset, so the schedule looks stable across restarts.

Per-company Postgres databases

Three resolution layers — first non-empty wins.

version: 1
companies:
# Uses the explicit binding override.
acme:
knowledge:
store: postgres
name: knowledge:acme
config:
dsn_env: BRIAR_ACME_KB_PG # 1️⃣ wins
# Falls back to the per-company env var (auto-derived).
widgets:
knowledge:
store: postgres
name: knowledge:widgets # reads BRIAR_WIDGETS_DATABASE_URL 2️⃣
# No company override → uses BRIAR_DATABASE_URL global.
zenith:
knowledge:
store: postgres
name: knowledge:zenith # reads BRIAR_DATABASE_URL 3️⃣

Audit credential coverage before running

$ briar secrets doctor --examples runbooks/
# Walks every (company × extractor × provider) and (company × messages × writer)
# tuple. Names the missing env vars. Doesn't print values.

Turn a CLI command into YAML

Take the command line, strip briar extract --company X, and put the remaining flags under args: after renaming - to _. Repeatable flags become lists.

# CLI form
briar extract --company acme \
--include pr-archaeology \
--pr-repo acme/widgets --pr-repo acme/api \
--pr-max 50 \
--pr-authors-block "renovate[bot]"
# YAML form
companies:
acme:
knowledge: { store: file, name: knowledge:acme }
extract:
- name: pr-archaeology
args:
pr_repo: [acme/widgets, acme/api]
pr_max: 50
pr_authors_block: ["renovate[bot]"]

Validating a runbook

Three increasingly thorough checks.

1. Parse-only

$ python -c "
$ from pathlib import Path
$ from briar.iac.runbook.executor import RunbookLoader
$ rb = RunbookLoader.load(Path('runbooks/acme.yaml'))
$ print(f'companies: {sorted(rb.companies.keys())}')
$ "

Bad YAML or unknown extractor names raise a locator-aware Pydantic error pointing at the line and field.

2. Credential audit

$ briar secrets doctor --examples runbooks/

Reports every missing env var by extractor and writer.

3. Dry-run a single task

$ briar runbook extract runbooks/acme.yaml --task nightly

Runs once, end-to-end. The result is written to the configured knowledge store; nothing is scheduled.

Roundtrip-safe

Briar's loader uses Pydantic with strict mode on the outer shape and forgiving mode on the company entry — legacy fields like profile:, workspace_id:, and knowledge_file: still load even if you stop using them, so you can move forward without breaking old YAMLs you keep around for reference.

See also

  • briar runbook — the subcommands that consume these YAMLs.
  • briar extract — the imperative form of every args: key.
  • Plugin registries — every extractor / writer / provider name you can use.
  • Configuration — per-company env var naming convention and credential stores.
  • Recipes — full multi-host deployment walkthroughs.