Jobs, records, ingest, maps, and routines¶
Brain Framework configures every executable job under top-level jobs: in bf.yaml (defaults: sets shared harness, timezone, and post). Jobs run only via explicit execution commands on one selected brain.
Your first sensor¶
Complete Getting started, including its collection exercise, then work inside ~/brain. The reviewed local documents sensor reads only the folder passed to it and bounds file sizes and output. Move the brief into its own folder, then copy the script from the release tag matching bf --version:
mkdir -p inputs/website-demo
mv inputs/brief.txt inputs/website-demo/brief.txt
curl -fsSLo sensors/local-documents.py "https://raw.githubusercontent.com/fmind/brain-framework/v$(bf --version)/examples/sensors/local-documents.py"
chmod +x sensors/local-documents.py
Review the downloaded script before running it; chmod +x restores the executable bit BF needs to run a script directly.
Configure a sensor¶
In bf.yaml, replace the brief entry under jobs: with this one. Keep version, name and other settings:
# https://fmind.github.io/brain-framework/docs/jobs/
jobs:
local-documents:
run: [sensors/local-documents.py, website-demo, "{{brain}}/inputs/website-demo"]
emit: records
mode: snapshot
website-demo is the stable label used in record ids, and the last argument selects the demo folder. The script needs Python 3.11 or later as python3 (Job settings).
Collect and read¶
bf collect local-documents --dry-run
bf collect local-documents
bf search "product explanation" --scope memories/local-documents
bf read local-documents:website-demo/brief.txt
The dry run executes the script and previews up to three records (added, updated, unchanged, removed) without saving them. The real collection stores one record under memories/local-documents/, and the exact read includes:
{
"ref": "local-documents:website-demo/brief.txt",
"record": {
"id": "website-demo/brief.txt",
"title": "brief.txt",
"text": "Visitors need a clear product explanation and pricing before signing up.\n",
"attributes": { "scope": "website-demo" }
}
}
In projects/new-website.md, point the Evidence line beneath the decision at the new record:
Evidence: [Product brief](local-documents:website-demo/brief.txt).
This edit clears the review flag on the project. Remove the retired brief records and tutorial script, then validate:
rm -r memories/brief sensors/brief.py
bf validate
Validation reports "records":1 and "valid":true.
Selected highlights¶
Use examples/sensors/highlights.py to ingest curated passages (@type: "Highlight", url, relations.source-document, attributes.locator, and non-FTS attributes.annotation) from portable highlight exports; see examples/sensors/README.md for local documents, highlights, Git history, GitHub history/issues/PRs, Google Calendar, Gmail headers, and Google Drive folders.
Job settings¶
Each entry under jobs: names [a-z][a-z0-9-]* (never bf) and declares run:, prompt:, or send: (optional only when enabled: false):
| Field | Default | Role |
|---|---|---|
emit |
log |
Output sink: records, messages, message, action, or log. |
source |
job name | SOURCE in SOURCE:ID and memories/SOURCE/ (emit: records only; must be unique). |
mode |
window |
window (overlap/reconcile), cursor ({{cursor}}), or snapshot (complete catalog; records/messages only). |
lookback |
86400 |
Initial window or snapshot span before --until (1s–365d). |
overlap |
300 |
Seconds re-read before the previous window end (0s–365d). |
reconcile |
none | Periodic wider backfill { every, lookback } on mode: window record jobs. |
priority |
normal |
low summarizes high-volume record sources under sources on period pages and halves search weight. |
silent_after |
none | Elapsed seconds without new/changed records before a scheduled source sets "silent": true. |
max_text_bytes |
none | Truncates record.text on a UTF-8 boundary (1–4,194,304) with attributes.partial: true; unset fails > 4 MiB. |
expire_after |
none | Retention age (1h–100y) pruning old records during complete collections. |
keep |
none | Keeps newest N records or unedited generated actions (1–50,000). |
requires |
{} |
{ bins, env } checked by bf doctor and host environment variables forwarded to the job. |
timeout |
300 |
Process timeout in seconds (1–3,600); kills process group on expiration. |
max_bytes |
64 MiB | stdout byte cap (1 B–256 MiB). |
- Command paths: argv index
0must be a bare binary name onPATHor a relative path undersensors/,routines/, orchannels/(executable bit set). Usecwdfor a brain-relative working directory. - Placeholders:
{{brain}},{{home}},{{state}}(.bf/state/JOB/, mode700),{{run}},{{start}},{{end}},{{cursor}},{{context}}, plus{{lines:relative/path.txt}}(expands non-blank, non-#lines as separate arguments, up to 128 argv entries).
Credentials and isolation¶
Subprocesses run with no shell (shell=False), stripped loader/startup-injection variables (LD_*, PYTHON*, BASH_ENV, NODE_OPTIONS, etc.), bounded logs (logs/NAME.log), and clean environment forwarding (BF_BRAIN + requires.env). Prefer credentials in order: (1) provider CLI logins (gh, gws, gcloud), (2) private mode-600 files outside the brain ("{{home}}/.config/service/token"), (3) interactive OS keyrings. Never put secrets in bf.yaml or argv.
Collect, backfill, partial coverage, and bf forget¶
bf collect JOB --since 7d --until now --dry-run
bf collect JOB --allow-removal
bf collect JOB --input sample.jsonl
bf forget SOURCE:ID --dry-run
- Atomic writes & removal guard: failed runs never change
memories/. Amode: snapshotrun that empties a catalog or drops more than half (>10 records) aborts unless--allow-removalis passed once. - Partial coverage: printing
{"records": [...], "incomplete": {"missed": 1, "of": 3}, "cursor": "..."}saves returned records without deleting missing snapshot items; 3 consecutive incomplete runs flag"degraded"inattention. bf forget: deletes records and records SHA-256 tombstones inmemories/SOURCE/.forgotten.- Decoupled runtime: record collection never opens
.bf/runtime.sqlite3; committed changes touch.bf/wakeupsobf watch/ runtime picks them up cleanly.
Direct ingestion (bf ingest)¶
Push JSON arrays, JSON Lines, or {records, incomplete, cursor} payloads from stdin or files directly into memories/SOURCE/ without a scheduled run: command:
python3 exporter.py | bf ingest alerts --dry-run
bf ingest alerts --input /tmp/alerts.jsonl --snapshot
bf ingest validates records, applies map:, expire_after, and keep if a matching emit: records job targets that source, enforces --allow-removal when --snapshot shrinks a catalog, pings .bf/wakeup, and never opens .bf/runtime.sqlite3.
Declarative map: and reprojection¶
When a CLI already outputs clean JSON or JSONL, declare RFC 6901 JSON Pointers under map: (items, cursor, id, @type, title, text/body, url, created/time, updated, relations, attributes, {path, prefix, lower} transforms, and /*/ array expansion) instead of writing a Python script:
jobs:
github-issues:
run: [gh, issue, list, --repo, OWNER/REPO, --json, number, title, body, state, url, updatedAt]
emit: records
mode: snapshot
map:
id: { path: /number, prefix: "OWNER/REPO/issues/" }
"@type": Issue
title: /title
text: /body
url: /url
updated: /updatedAt
Re-validate already-stored records after updating bf.yaml fields: rules via bf build --reproject JOB [--dry-run].
Deterministic routines (bf run)¶
Routines use run: with emit: log (default) or emit: action (printing validated draft OKF ACTION.md):
bf run weekly-review -- --flag
bf run --hook pre-commit
Pass -- before routine arguments starting with -. Copy reviewed routines from examples/routines/: weekly-review.py, evidence-health.py (reports broken links, stale sources, and source health), and bf validate wired to on: { hook: pre-commit }.