Grain Types
What are grain types?
Every memory stored in the Areev context database is classified as one of 10 grain types defined by the Open Memory Schema (OMS) 1.2. The grain type determines which fields a memory carries, how it is indexed, and how AI agent memory recall surfaces it in queries.
Grain types enforce structure at the storage layer. When you write a grain, Areev validates the type-specific fields, serializes the grain into the .mg binary format with a single-byte type header, and indexes it for retrieval. When you recall grains, the type constrains which fields are available for filtering and how the text representation is built for BM25 and vector search. This structure is what separates an autonomous memory system from an untyped key-value store: each grain carries semantic meaning that agents can reason over.
The 10 types cover the full lifecycle of agent cognition — from perceiving the world (Observation) to forming knowledge (Belief), making decisions (Reasoning, Consensus), taking action (Action, Workflow), tracking objectives (Goal), managing state (State, Event), and handling compliance (Consent). The first seven types originated in OMS 1.1 under different names; Reasoning, Consensus, and Consent were introduced in OMS 1.2.
| Byte | Type | Serialized Name | Purpose |
|---|---|---|---|
0x01 | Belief | belief | Structured knowledge triple (subject/relation/object) |
0x02 | Event | event | Timestamped occurrence or message |
0x03 | State | state | Agent state snapshot or checkpoint |
0x04 | Workflow | workflow | Directed-graph procedural plan (nodes + edges) |
0x05 | Action | action | Tool invocation or operation record |
0x06 | Observation | observation | Cognitive observer perception |
0x07 | Goal | goal | Agent objective with satisfaction criteria |
0x08 | Reasoning | reasoning | Inference chain and thought audit trail |
0x09 | Consensus | consensus | Multi-agent agreement record |
0x0A | Consent | consent | DID-scoped permission grant or withdrawal |
How do I add a grain to the context database?
Pass the grain_type string and the type-specific fields to either endpoint. The /add endpoint accepts any grain type but is optimized for Belief grains — it defaults to grain_type: "belief" and accepts a flat {subject, relation, object} body. For other grain types, pass grain_type plus the type-specific keys (e.g. content for an event, description for a goal); unrecognized fields are forwarded via the fields map. The /batch-add endpoint shares the same write pipeline and is preferred when writing multiple grains in a single request.
When using the Python SDK, db.add() accepts the grain type as its first argument and a dict of type-specific fields as its second argument. The method returns a content-address hash that uniquely identifies the grain. For HTTP, Belief grains commonly use /add with a flat JSON body; other types may use either /add (with explicit grain_type) or /batch-add with a grains array where each entry specifies grain_type and fields.
Areev validates fields against the OMS 1.2 schema before writing. Required fields that are missing cause a 400 error. Optional fields default to null or their documented defaults (for example, goal_state defaults to "active").
import areev
db = areev.open("./data")
# Belief -- triple-indexed in the graph index
db.add("belief", {"subject": "john", "relation": "likes", "object": "coffee"})
# Event -- free-text content
db.add("event", {"content": "User completed onboarding"})
# Action -- tool call with result
db.add("action", {"tool_name": "web_search", "input": {"q": "weather"}, "content": "72F sunny"})
POST /api/memories/default/add
Content-Type: application/json
{"subject": "john", "relation": "likes", "object": "coffee"}
POST /api/memories/default/batch-add
Content-Type: application/json
{
"grains": [
{"grain_type": "event", "fields": {"content": "User completed onboarding"}},
{"grain_type": "action", "fields": {"tool_name": "web_search", "content": "72F sunny"}}
]
}
Which grain type should I use?
Choose based on what the memory represents, not how you plan to query it.
- Storing knowledge claims (who/what/how relationships) — use Belief. Its subject/relation/object triple is indexed in the graph index for graph queries.
- Recording things that happened (messages, logs, conversations) — use Event. Events are optimized for temporal queries.
- Saving agent checkpoints (session state, config snapshots) — use State. States carry arbitrary JSON and support supersession for updates.
- Encoding procedures (runbooks, multi-step plans) — use Workflow. Workflows are directed graphs with
nodes,edges, optionalbindings,retries, and atrigger. - Logging tool calls (API calls, code execution, computer use) — use Action. Actions track tool name, input, output, errors, and duration.
- Capturing perceptions (sensor data, LLM observations, multi-agent views) — use Observation. Observations track observer identity and scope.
- Tracking objectives (tasks, targets, milestones) — use Goal. Goals have state transitions (active/satisfied/failed/suspended) and priority levels.
- Preserving inference chains (decision audit trails, thought traces) — use Reasoning. Reasoning grains store premises, conclusions, and alternatives.
- Recording multi-agent decisions (votes, quorum outcomes) — use Consensus. Consensus tracks participating observers, agreement/dissent counts, and thresholds.
- Managing permissions (data-subject consent, access grants) — use Consent. Consent grains are DID-scoped and purpose-bounded for compliance.
What fields do all grains share?
Every grain type inherits a set of common fields from GrainCommon in addition to its type-specific fields. These common fields control indexing, access, temporal validity, and inter-grain relationships across the entire AI memory system.
The confidence field (0.0 to 1.0) lets agents express certainty. The namespace and tags fields scope queries and organize grains into logical groups. The superseded_by field and supersession mechanism support immutable updates — rather than modifying a grain in place, you create a new version and link the old grain to it. Bi-temporal fields let you model business time (valid_from, valid_to, temporal_type) separately from storage time (system_valid_from, system_valid_to).
Fields like related_to and content_refs create explicit links between grains and to external resources. The verification_status field tracks whether a grain has been independently verified, contested, or retracted. The invalidation_policy field protects regulated grains from premature deletion.
| Field | Type | Default | Description |
|---|---|---|---|
namespace | string | null | Logical grouping for scoped queries |
user_id | string | null | Owner user ID for access control and erasure |
tags | string[] | [] | Searchable labels |
confidence | float | 1.0 | Trust score from 0.0 to 1.0 |
source_type | string | null | Origin indicator (e.g., "user", "agent", "system") |
importance | float | null | Priority weight for recall ranking |
temporal_type | string | null | Bi-temporal model: "state", "event", or "interval" |
valid_from | int | null | Start of business validity (Unix timestamp) |
valid_to | int | null | End of business validity (Unix timestamp) |
system_valid_from | int | null | Start of storage (transaction) time — when the grain became known to the system (Unix timestamp) |
system_valid_to | int | null | End of storage (transaction) time — when the grain was superseded in storage (Unix timestamp) |
verification_status | string | null | "unverified", "verified", "contested", or "retracted" |
embedding_text | string | null | Custom text override for vector embedding (max 8192 bytes) |
related_to | object[] | [] | Links to other grains by hash with relation type and weight |
content_refs | object[] | [] | References to external content (URIs, MIME types, checksums) |
superseded_by | string | null | Hash of the grain that replaced this one |
supersession_justification | string | null | Human-readable reason this grain was superseded |
supersession_auth | object[] | null | Authorization records backing the supersession |
invalidation_policy | object | null | Protection rules for regulated grains |
embedding_refs | object[] | [] | References to stored vector embeddings for this grain |
provenance_chain | object[] | [] | Ordered record of how this grain was derived |
derived_from | string | null | Hash of the grain this one was derived from |
author_did | string | null | Decentralized identifier (DID) of the author |
origin_did | string | null | DID of the originating agent or system |
origin_namespace | string | null | Namespace the grain originated in |
consolidation_level | int | null | Memory-consolidation tier, raised as a grain is reinforced |
success_count | int | null | Times this grain (e.g. an Action) has succeeded |
failure_count | int | null | Times this grain has failed |
context | object | null | Arbitrary structured context attached to the grain |
created_at | int | null | Creation timestamp (Unix) |
extra_fields | object | {} | Custom fields preserved through the serialization round-trip |
How did grain type names change from OMS 1.1 to 1.2?
OMS 1.2 renamed four grain types from OMS 1.1 to better reflect their semantics. The byte codes remain the same, but only the canonical OMS 1.2 names are accepted — the old OMS 1.1 type names are rejected.
The renames are: Fact became Belief (0x01), Episode became Event (0x02), Checkpoint became State (0x03), and ToolCall became Action (0x05). The Action grain also changed three field names: arguments became input, result became content, and success became is_error (with inverted boolean logic — success: true maps to is_error: false). Areev accepts only the 10 canonical OMS 1.2 lowercase names (belief, event, state, workflow, action, observation, goal, reasoning, consensus, consent); the old OMS 1.1 type names (fact, episode, checkpoint, toolcall) are rejected with a 400 “unknown grain type” error, and the old Action field names (arguments, result, success) are ignored. There is no deprecation-warning mechanism — update any existing code to the canonical names.