Records
A record is one piece of project knowledge: a decision, a fact, a command, an open question. Records are what a brief is made of. Most records belong to exactly one project. Lessons and preferences can also live in your personal store. Every record is stored privately on your disk.
Anatomy of a record
Section titled “Anatomy of a record”devtwin record add --pilot "$PILOT" --scope example --project example.atlas \ --kind decision \ --title "Use server-side search" \ --body "Filter on the server because the list can grow." \ --source file:README.md \ --request-id req_atlas_search_01 --apply| Part | What it is | Limits |
|---|---|---|
| Kind | What sort of knowledge this is. See kinds. | One of twelve values |
| Status | How much weight it carries. See statuses. | Defaults to observed |
| Title | A one-line summary. | 1 to 200 characters, no line breaks |
| Body | The detail, as plain text or Markdown. | Up to 32,768 bytes |
| Sources | Evidence the record is based on. See sources. | Up to 20 |
| Request ID | Your name for this write, used to make it repeatable. | req_ plus 8 to 64 of a-z, 0-9, _, - |
devtwin adds the rest: a record ID derived from the scope, project and request ID, hashes of the record and body, a creation time, and who recorded it.
| Kind | Use it for |
|---|---|
brief | A short description of the project. Onboarding writes these from the README. |
constraint | Something that must stay true, such as “the repository’s own AGENTS.md governs”. |
decision | A choice that was made, and why. |
map | Where things live in the codebase. |
command | How to build, test or run something. |
environment | A named environment, such as staging. Names only, never credentials. |
fact | Something observed to be true. |
experiment | Something tried, and what happened. |
handoff | Where a session stopped and what comes next. See Handoffs. |
unknown | An open question that someone still needs to answer. |
lesson | Advice that applies to some files, packages or topics. See Lessons and hooks. |
preference | How you like to work. It follows you across projects, so it never lives in a project store. |
Briefs list records in this order of kind, so constraints and decisions come before commands and facts, and open questions come last.
Class, reach and match
Section titled “Class, reach and match”New records use schema 2, which adds three fields. Readers accept schema 1 and 2, and a schema 1 record is never rewritten.
| Field | Values | Rule |
|---|---|---|
class | fact or lesson | A fact stays in its project. A lesson record always has class lesson. |
reach | personal, project or org | A fact has project reach. A preference never has project reach. |
match | paths, packages and topics | At most 20, 20 and 10 entries. Only a lesson or preference with a match block reaches an agent through the hook. |
Each reach has its own home:
| Reach | Home | Holds |
|---|---|---|
project | The project’s records | Every kind except preference |
personal | Your personal store, data/personal/records/ | lesson and preference. It is part of every scope’s lesson index. |
org | The scope’s org home, data/scopes/<scope>/org/records/ | lesson. No command writes here yet: for a scope with a hub, the hub holds the organization’s copy. |
Statuses
Section titled “Statuses”| Status | Meaning | In the brief? |
|---|---|---|
observed | Someone saw or decided this. The default. | Yes |
confirmed | The owner reviewed and confirmed it. Needs --reviewer. | Yes, ahead of other records of the same kind |
disputed | Someone disagrees; treat with care. | Yes, and listed in disputedIds |
proposed | A draft awaiting review. | No, only its ID and title under review |
To confirm a record, pass a reviewer reference:
devtwin record add --pilot "$PILOT" --scope example --project example.atlas \ --kind constraint --status confirmed --reviewer github:your-handle \ --title "Node 22 or later" --body "The service runs on Node 22 LTS." \ --request-id req_atlas_node_01 --applyWithout --reviewer, the command fails with DEVTWIN_APPROVAL_REQUIRED. Reviewer references take the form email:, github:, slack:, linear: or handle: followed by an identifier.
Sources
Section titled “Sources”Sources say where a record’s evidence came from. Pass --source once per source.
| Form | Example |
|---|---|
file:<path> | file:README.md, a path relative to the repository root |
commit:<sha> | commit:1791ed0 (7 to 64 hex characters) |
record:<id> | record:rec_94315938d3e27ab174b5665d |
issue:<KEY-123> | issue:ATLAS-42 |
user:<ref> | user:github:your-handle |
url:<https URL> | url:https://example.com/adr/12, with no query string, fragment or credentials |
File sources drive the freshness label in a brief. Files that onboarding read are stored with a hash, so the brief can say current while the file is unchanged and needs-revalidation once it changes. A file: source you add by hand carries no hash, so the brief marks it needs-revalidation. A file that onboarding did not read shows as unbound-source. See freshness.
Records are immutable
Section titled “Records are immutable”Once written, a record never changes. When something changes, write a successor that supersedes it. You need the old record’s ID and its current hash, both of which appear in the brief:
devtwin record supersede --pilot "$PILOT" --scope example --project example.atlas \ --target rec_94315938d3e27ab174b5665d \ --expect-hash sha256:b411bd1f8c215be10b51c23b52ecac6614a9c3a1d627502e237f5f78e5988423 \ --kind decision --title "Use server-side search with paging" \ --body "Filter on the server and page results at 50 items." \ --request-id req_atlas_search_02 --applyThe old record drops out of briefs; the new one takes its place. Both stay on disk, so the history is never lost. The hash check makes sure you are replacing the version you think you are: if someone already superseded it, the command fails with DEVTWIN_STALE_VERSION.
You can pass --target and --expect-hash more than once to replace several records with one.
Request IDs make writes safe to repeat
Section titled “Request IDs make writes safe to repeat”The record ID comes from the scope, project and request ID. That means:
- Running the same command twice returns the existing record with
"created": false. - Reusing a request ID with different content fails with
DEVTWIN_CONFLICT.
So pick a new request ID for each new record, and reuse it only to retry.
Propose now, publish later
Section titled “Propose now, publish later”Sometimes you want a record reviewed before it counts, for example when an agent drafts it. record propose writes a private proposal file instead of a record:
devtwin record propose --pilot "$PILOT" --scope example --project example.atlas \ --kind fact --title "Tests use node:test" \ --body "package.json declares node --test." --source file:package.json \ --request-id req_atlas_tests_01 --apply{"ok":true,"command":"record propose","result":{"proposalId":"prop_ff03889979eda8c380fdaea8","path":"/private/tmp/devtwin-onboard.vgS21y/data/scopes/example/projects/example.atlas/proposals/prop_ff03889979eda8c380fdaea8.json"}}A proposal does not appear in briefs. When you have reviewed it, publish it:
devtwin record publish --pilot "$PILOT" --scope example --project example.atlas \ --proposal prop_ff03889979eda8c380fdaea8 --reviewer github:your-handle --applyPublishing turns the proposal into an observed record. The bundled devtwin-record skill works this way: the agent proposes and you publish. See Capture decisions.
What records refuse
Section titled “What records refuse”devtwin scans every title and body for text that looks like a secret, such as private keys, authorization headers, key-and-token assignments, JWTs, and GitHub, AWS or Slack tokens. A match fails with DEVTWIN_SENSITIVE_CONTENT and nothing is written:
{"ok":false,"command":"record add","error":{"code":"DEVTWIN_SENSITIVE_CONTENT","message":"Sensitive content refused","details":{"types":["credential-assignment"]}}}This check is a tripwire, not a guarantee. Never store credentials in records; keep them in your password manager or credential store.