Notes, records and fields¶
This reference defines the files BF reads: bf.yaml, OKF notes, record files and the shared fields that connect them. The link reference owns address syntax. For worked examples, start with Linking knowledge.
A minimal bf.yaml declares the brain format and a stable name:
# https://fmind.github.io/brain-framework/
version: 8
name: brain
version is the brain format of bf.yaml, evals/*.yaml, record files and the memories/ layout. It is independent of the package version. A missing or different version fails before any other field, naming the supported one: bf.yaml declares version 6; this release reads version: 8. The changelog lists the upgrade steps of each format.
Add fields, sensors, routines, assistants, channels, related brains, watch preferences and a validate policy only as needed. bf init writes the version, the name and the starter relations.
A program's command starts with either:
- a bare command name found on PATH;
- or a normalized path below
sensors/,routines/orchannels/.
Absolute paths, other folders, .. and placeholders in that first position are errors.
Program names¶
Sensors, routines and assistants are named by their keys in bf.yaml. Each name is unique across all three, because:
- programs share logs and locks by name;
- a sensor's or assistant's name is also the source of its records, as in
brief:website-brief.
A clash fails naming both entries, such as sensors.mail and assistants.mail: program names must be distinct.
| Program | Name |
|---|---|
| Sensor, assistant, channel | A lowercase letter, then lowercase letters, digits or hyphens. |
| Routine | Lowercase letters and digits in words joined by single hyphens, starting with a letter; it also names the action folders the routine writes. |
No program, whether sensor, routine, channel or assistant, may be named bf. Refs starting with bf: read as BF addresses, so a sensor's or assistant's records could not be read by ref.
bf.yamlthen fails to load, assensors.bf: the name bf is reserved for BF addresses; rename the sensor and move memories/bf/ to its new name.- Records left in
memories/bf/are skipped and reported until you move them.
Limits gives the length bound.
Editor schemas¶
Print the JSON Schema of an installed format, without selecting a brain or using the network:
bf schema
bf schema --kind registry
bf schema --kind eval
Their title values are Config, UserConfig and Suite. --kind search-reply and --kind read-reply print the reply schemas. Save the output and associate it with your editor's YAML validation. --kind assistant-reply prints the structured reply an assistant's harness returns.
| File | Published schema |
|---|---|
bf.yaml |
Brain |
Machine registry bf/config.yaml |
Registry |
evals/*.yaml |
Evaluation |
The schemas check names, types, limits and structure. BF also checks meanings that depend on the brain, such as declared fields, distinct program names and valid identities: run bf validate and bf update --dry-run.
YAML parsing rejects duplicate keys, anchors, aliases, unsafe tags and oversized documents. Configuration and frontmatter follow the YAML 1.2 core schema:
- Only
trueandfalse, also spelledTrue,TRUE,FalseorFALSE, are booleans. onandnoare strings.017is decimal.1:30is text.
Note format¶
Projects, concepts and ACTION.md notes are OKF notes. Write notes and actions describes their fields; these rules settle edge cases:
typemust be nonempty.status, when set, must bedraft,stableordeprecated; omitted meansstable.bf validateenforces both.- Frontmatter must be valid YAML closed by a
---line. The title must be nonblank and at most 4,096 characters. A note breaking either rule is invalid and unsearchable, reported underproblems. - A frontmatter key with an invalid value also makes the note invalid and unsearchable, such as
tagsthat is not a list orupdatedthat is not a realYYYY-MM-DD. - So does a link that cannot be parsed, such as
http://[your-host]/or abf://link with malformed encoding, another query key or an empty#fragment. The problem names itsline Nor frontmatter key, such aslinks. stale_afteris an ISO 8601 date-time with a timezone; a date alone is invalid.aliasesare namespacedscheme:valueidentities, never display names. A URI inresourceis an identity too.- Entities, aliases and resources follow the identity rules.
aliases,linksandtagshold at most 1,000 items each.fieldsholds values of fields declared inbf.yaml, checked like a record's. An invalid value claims nothing and failsbf validate, while the note stays searchable.- A declared relation written at the top level instead of under
fieldsis reported, because OKF ignores unknown keys. sourcesentries need a nonemptyresource. Only one written as a link, without spaces, is cited and checked; other text may describe a population.verifiedevents needbyandat.
In projects/ and concepts/, index.md lists a folder's notes and permits only okf_version at a folder root; log.md groups changes under YYYY-MM-DD headings without frontmatter. Neither gets reminders or tasks.
Other Markdown, such as files in an action's inputs/ and outputs/, stays ordinary:
- Only valid
title,type,status(any word),updated,summaryanddescriptionapply. - Identities, tags, fields,
sourcesandstale_afterapply only to OKF notes, so a copied document cannot claim an identity. - Frontmatter that is not valid YAML is ignored, and the file is searched without it.
- A frontmatter block that never closes is searched as text.
- A title longer than 4,096 characters, or blank, gives way to the file name.
- A link URL that cannot be parsed, such as
http://[your-host]/, connects nothing while the file stays searchable. - A malformed
bf://link still makes the file invalid.
In OKF and ordinary Markdown alike, Git conflict markers (<<<<<<<, |||||||, >>>>>>>) at the start of a line make the file invalid and unsearchable, even inside a code block: indent them by one space to show them. A leading byte order mark is ignored.
Record revisions and provenance¶
A source keeps one record per id. Collecting the same id again replaces it; its file, named by the SHA-256 of the id, stays the same. History belongs in Git or backups.
| Field | Meaning | Example |
|---|---|---|
time |
When the event happened. | A message was sent on 2026-09-25 at 09:00. |
attributes.updated |
When the provider last modified it. | Its author edited it on 2026-09-27. |
attributes.observed |
When BF first collected this revision. | BF received the edit 15 minutes later. |
attributes.partial |
true when the text is deliberately incomplete. |
The sensor kept only an excerpt. |
These timestamps need a timezone.
- The edited message stays in its event period and appears in the
changedlist of the day it was modified. - Like a new event, an upstream edit made after a linked note's last edit flags that note for review.
observednever does. - Collection keeps
observedwhile the content is unchanged. - When both revisions declare
updated, an older revision cannot replace a newer one. - That rule skips a stored revision whose
updatedis later than itsobserved, as from a clock or file date in the future: such a revision never freezes the record.
updated, observed and partial are reserved: BF validates their types and sets observed. Map provider fields to them explicitly, such as Drive's modifiedTime to updated, rather than passing a raw payload.
Only <sha256-id>.json files under memories/SOURCE/ are records. A file whose id does not match its name is invalid:
bf validatereports it;- search skips it;
- collection never replaces or removes it.
bf validate also reports other visible files under memories/.
Shared fields and sensor mappings¶
Fields give provider-specific values a shared meaning:
- You declare them under
fields:inbf.yaml. - Each sensor's
fieldsmaps its output into them. - Collection validates the values and builds the declared relations.
The four-tool example maps four differently named project fields to one identity. BF never learns fields or infers identities from prose.
For example, map a Git record's author identities to author, and label its kind:
# https://fmind.github.io/brain-framework/
version: 8
name: brain
fields:
author:
description: Account explicitly credited as the author.
type: identity
cardinality: many
relation: true
examples: [[person:email/alice@example.test]]
kind:
description: Common kind of source item.
type: string
cardinality: one
examples: [commit, message, document]
sensors:
git-commits:
command: [sensors/git-history.py, "{{home}}/code", "{{start}}", "{{end}}"]
fields:
author: { path: /attributes/author_refs }
kind: { value: commit }
For a commit by Alice, the reviewed Git history sensor prints "attributes": {"author_refs": ["person:email/alice@example.test"]}. The address is fictional: the sensor itself skips commits by authors at reserved test domains such as example.test. BF stores "fields": {"author": ["person:email/alice@example.test"], "kind": "commit"} in the record, and author becomes a relation from the commit to Alice's identity.
Standard vocabulary¶
Sensors written by different people line up when they print the same attribute names. bf init declares these fields with a default path, so a sensor printing them needs no fields: setting in a new brain:
| Field | Type, cardinality | Default path | Meaning |
|---|---|---|---|
author |
identity, many, relation | /attributes/author_refs |
Account explicitly credited as author; not ownership. |
owner |
identity, many, relation | /attributes/owner_refs |
Account or organization the provider declares responsible. |
assignee |
identity, many, relation | /attributes/assignee_refs |
Account assigned responsibility for the item. |
participant |
identity, many, relation | /attributes/participant_refs |
Any account in a message, event or meeting, including the roles below. |
sender |
identity, many, relation | /attributes/sender_refs |
Account that sent the message; narrower than participant. |
recipient |
identity, many, relation | /attributes/recipient_refs |
Direct recipients, without copies; narrower than participant. |
cc |
identity, many, relation | /attributes/cc_refs |
Accounts copied on a message; narrower than participant. |
organizer |
identity, many, relation | /attributes/organizer_refs |
Organizer declared by a calendar; narrower than participant. |
attendee |
identity, many, relation | /attributes/attendee_refs |
Accounts listed as attendees, not proof of attendance; narrower than participant. |
repository |
identity, many, relation | /attributes/repository_refs |
Repository the item belongs to. |
project |
identity, many, relation | /attributes/project_refs |
Project the source names for the item. |
kind |
string, one | /attributes/kind |
Provider-declared kind within a source mixing several kinds. |
status |
string, one | /attributes/status |
The item's state in its own tool. |
bf init also declares the note relations depends-on, related-to and supersedes, without a path. Identity values follow the formats the reviewed sensors use: person:email/ with a lowercase address, person:HOST/LOGIN for a provider account such as person:github.com/octocat, and lowercase repo:HOST/OWNER/NAME. A sensor's own fields: mapping still overrides a default path, as the four-tool example shows for differently named project fields. A default path applies to every sensor of the brain: a sensor printing attributes.status as an object, or author_refs as a single string, fails its whole collection until it prints the declared type or the field's path changes. A brain created before this vocabulary keeps its own fields: add the rows it lacks to use sensors that print them.
Types and cardinality¶
| Setting | Rule |
|---|---|
description |
Required. |
type |
Required: string, integer, number, boolean, timestamp or identity. |
cardinality |
one, optional or many; defaults to optional. |
relation |
Defaults to false. A relation needs type: identity. |
examples |
None by default. |
| Field name | Starts with a lowercase letter and holds lowercase letters, digits or hyphens, up to 64 characters. tagged-with, links and cites are reserved. |
Types are strict:
"42"is a string, not a number.- Strings are nonempty, without control characters, up to 8,192 characters.
- A timestamp needs a timezone.
- An identity is an exact, case-sensitive
scheme:valuewithout invisible characters, as the identity rules define. Sensors normalize it, such as lowercasing an email address.
| Cardinality | Mapped value |
|---|---|
one |
One value; a missing or null value fails the whole collection. |
optional |
One value when present; missing or null omits the field. |
many |
A list of up to 1,000 values, duplicates removed; missing or null omits it, an empty list records none. |
- A note's
fieldsfollow the same types and cardinality, whichbf validatechecks. Only collection requires aonevalue, so a note may omit it. - Each example is a complete field value checked against the type and cardinality. Examples never supply defaults.
- Listings and backlink previews show single-value fields of up to 200 characters, so sources that disagree appear side by side.
Narrower relations and allowed targets¶
A relation can name a broader relation and restrict its values to targets. For example, a calendar sensor maps organizers and attendees, and one relation page should list everyone who took part:
# https://fmind.github.io/brain-framework/docs/schema/
fields:
participant:
description: Person who explicitly took part in the event.
type: identity
cardinality: many
relation: true
targets: ["person:email/"]
organizer:
description: Person who explicitly organized the event.
type: identity
cardinality: many
relation: true
broader: participant
targets: ["person:email/"]
bf read person:email/alice@example.test --rel participant then lists her events through both relations, each item with its own relation. Map organizer from the sensor, not participant, or each person is stored twice.
| Setting | Rule |
|---|---|
broader |
Another declared relation without its own broader. It changes relation pages at read time, never stored records. |
targets |
1 to 64 quoted prefixes, each a scheme, a colon and optionally the start of the value, such as repo:github.com/. Collected values and record links must start with one as written; a note's typed links and fields may also name a target whose owner declares a matching identity. |
With targets, a collection whose mapped value falls outside them fails before saving. The error names the record's position and field, never the value: record 3: field organizer: identity outside the declared targets. bf validate reports stored records, note fields and typed links outside them.
Mapping rules¶
Each sensor's fields maps a declared field to exactly one path or literal value, overriding any default path declared on the field itself in top-level fields:. A path is a JSON Pointer into the sensor's record:
| Pointer | Reads |
|---|---|
/attributes/author_refs |
The author_refs attribute. |
/links |
The record's link list. |
/attributes/people/0 |
The first item in the people list. |
- Escape
/in a key as~1and~as~0. - Missing members are absent; traversing a scalar fails.
- There are no wildcards, expressions or implicit conversions.
Every record a sensor prints follows the fixed format below. A sensor cannot supply fields itself.
| Record key | Rule |
|---|---|
id |
Required, without control characters; it must still fit a BF address once percent-encoded. |
title |
Required, without control characters. |
text |
Optional. |
time |
Optional ISO 8601 timestamp with a timezone. |
url |
Optional, without control characters. |
links |
Identities or URLs. Other text is stored and searchable but connects only to a note whose path it spells exactly, such as projects/x.md; bf validate warns once per source. |
aliases |
Namespaced identities, never display names, following the identity rules. |
attributes |
JSON values; updated, observed and partial are reserved. |
- Limits bounds each key's length and count, and the size of the fields other than
text, including the mappedfieldsand theobservedtime BF adds. - Text must be valid Unicode: a lone surrogate, as from a non-UTF-8 file name, is rejected with its field named.
- One invalid record fails the whole collection before anything is saved. The error names the first one, such as
1.id: Field required. - Only mapped fields apply to a sensor.
bf validatechecks stored fields against the current declarations, including sources no longer configured.
Graph and evidence¶
After collecting a commit by Alice:
bf read person:email/alice@example.test
bf search "website" --scope person:email/alice@example.test
The read groups incoming links by relation, such as author, with their supporting records. The search covers the identity's owning note and the evidence linked to it.
- Each
relation: truefield creates claims from the record to its identity values, keeping the record as their origin. - Field values are searchable; field names and
attributesare not.
The graph lives in the disposable cache. bf export prints every claim.
- Replacing a record replaces its claims.
- Structural changes to
fields:, such as atypeorcardinality, rebuild the cache. description,examples,broaderandtargetsapply without a rebuild.- A field no longer declared as a relation adds no claims.
bf validatenames stored values the current fields reject.
Reproject stored records¶
After renaming a field or changing a mapping, apply the current mappings to the records a sensor already collected, without running it:
bf build --reproject git-commits --dry-run
bf build --reproject git-commits
For example, rename author to creator in fields: and in the sensor's mapping:
bf validatereportsfield author is not declared in bf.yaml fieldsfor each stored commit.- The preview counts the changes without writing, such as
{"sensor":"git-commits","dry_run":true,"records":2,"changed":2,"unchanged":0,"failed":0}. - The second command rewrites those records'
fieldsand refreshes the cache. - Validation then passes, and
bf read person:email/alice@example.test --rel creatorlists the commits.
Reprojection reads each record as its sensor printed it and keeps every other value, including observed. It never removes a record.
It cannot map a value the sensor never printed. A record the mappings reject:
- keeps its fields;
- counts as
failed; - appears under
problems(up to 200, then"problems_truncated":true); - makes the command exit 1.
Recollect those records after updating the sensor. Changes commit in transactions of up to 1,000 records. After an interruption, bf build recovers and a rerun completes the reprojection.