Skip to main content
Memory GrainMemory Grain
GitHub
All articles
memory-typestool-callsauditfunction-calling

Grain Type Deep Dive: Action — The Immutable Audit Trail for Agent Actions

OMS v1.2 update: The ToolCall type is now named Action with key field name changes. How OMS Action grains record every tool invocation, argument, and result as an immutable, content-addressed record — enabling replay, debugging, cost tracking, and compliance.

10 min read

Every modern AI agent interacts with the world through tools. It searches the web, queries databases, calls APIs, writes files, sends messages. Each of these actions has consequences — sometimes irreversible ones. Yet in most agent frameworks today, tool invocations vanish into logs that are unstructured, mutable, and disconnected from the agent's memory.

The Open Memory Specification addresses this with a dedicated grain type: Action. Defined in Section 8.5 of the OMS v1.2 specification, an Action grain is an immutable, content-addressed record of a single tool invocation — what was called, with what input, what came back, and whether it errored. Every invocation is a separate grain, never modified after creation.

This post covers the Action schema in detail, its field compaction keys, the parent task hierarchy mechanism, and real-world applications from API auditing to agent benchmarking.

What Is an Action Grain?

An Action grain is a record of tool/function invocation and result. It captures the complete lifecycle of a single action: the tool's name, the input passed to it, the content it returned, and whether it errored or succeeded.

Unlike a log line that might read "Called search API with query='climate data'", an Action grain is a structured, binary-serialized, content-addressed object. It has a deterministic SHA-256 hash. It can be cryptographically signed. It can be referenced by other grains — a Goal grain can point to Action grains as satisfaction evidence, or an Event grain can reference the actions that occurred during a conversation.

In the OMS header, Action grains carry type byte 0x05. This means any system scanning a stream of .mg blobs can identify Action grains by reading a single byte at offset 2 — no MessagePack deserialization required.

Required Fields

Every complete Action grain MUST include these six fields:

FieldTypeDescription
typestringMust be "action"
created_atint64 (epoch ms)When the invocation occurred
tool_namenon-empty stringName of the tool or function invoked (for function_call mode)
inputmapThe input passed to the tool (was arguments in v1.0/v1.1)
contentany (MessagePack-serializable)The value returned by the tool (was result in v1.0/v1.1)
is_errorboolTRUE means the invocation errored; FALSE means success (was success with inverted polarity in v1.0/v1.1)

The content field is deliberately typed as "any MessagePack-serializable value." This means it can be a string, a number, a map, an array, or even a nested structure — whatever the tool actually returned. OMS does not impose a schema on tool results because tools are inherently diverse.

The is_error field is a simple boolean. Note the polarity: is_error: false means the action succeeded, and is_error: true means it errored. This is an inversion from the old success field — code that checks success == true must now check is_error == false. It does not encode partial success or degree of completion — those semantics belong in the content or error_type fields.

Optional Fields

Action grains support several optional fields that add context to the invocation record:

FieldTypeDescription
action_phasestring"definition" | "call" | "result" | absent (complete record)
tool_call_idstringCorrelates a call-phase grain with its result-phase grain
execution_modestring"function_call" | "code_exec" | "computer_use"
error_typestringMachine-readable error category when is_error=true
errorstringHuman-readable error message when is_error=true
duration_msintExecution time in milliseconds
author_didstringDID of the agent that made the call
user_idstringAssociated data subject (for GDPR)
namespacestringMemory partition (default "shared")
parent_task_idstringContent address of a parent task grain
structural_tagsarray[string]Classification tags
content_refsarray[map]References to external content

The action_phase field is new in v1.2. When an Action grain records only one side of an invocation — the call arguments without a result yet, or the result without the original call — action_phase identifies which part of the lifecycle the grain represents. When absent, the grain is a complete record containing both input and content.

The error_type field is only meaningful when is_error is true. It carries a machine-readable error category — useful for grouping failures programmatically. This is distinct from error, which carries the human-readable message, and from content, which may still contain a structured error payload (like an HTTP error response body).

The duration_ms field records how long the action took in wall-clock milliseconds. This is a simple integer, not a float — millisecond precision is sufficient for action timing, and integers avoid the deterministic serialization complexities that floating-point values introduce.

Field Compaction Keys

OMS uses field compaction to minimize blob size. Human-readable field names are mapped to short keys before serialization. The Action-specific compaction keys, defined in Section 6.5, are:

Full NameShort KeyTypeNotes
tool_nametnstring
inputinpmapreplaces args (removed in v1.2)
contentcntanyreplaces res (removed in v1.2)
is_erroriserrboolreplaces ok (removed in v1.2)
action_phaseaphasestringnew in v1.2
tool_call_idtcidstringnew in v1.2
error_typeetypestringnew in v1.2
errorerrstring
duration_msdurint
parent_task_idptidstring

The old short keys args, res, and ok were removed in v1.2 alongside their full-name counterparts. Implementations that produce or consume those keys are non-conformant with v1.2. These compact keys are combined with the core field compaction (e.g., type becomes t, created_at becomes ca, namespace becomes ns). The mapping is bijective — one-to-one — and serializers MUST replace full names with short keys before encoding, while deserializers MUST reverse the mapping after decoding.

Building Task Hierarchies with parent_task_id

The parent_task_id field is a content address (SHA-256 hash) pointing to a parent task grain. This simple reference enables building task hierarchies — a complex task that spawns sub-actions.

Consider an agent tasked with "research and summarize recent papers on transformer architectures." This high-level task might decompose into:

  1. An Action to a search API to find papers
  2. An Action to a PDF extraction tool for each paper
  3. An Action to a summarization service
  4. An Action to format and store the final output

Each of these sub-actions can set its parent_task_id to the content address of the overarching task grain. This creates a tree structure that can be traversed at query time to reconstruct the full execution flow.

Because parent_task_id is a content address, the reference is cryptographically verifiable. You can prove that a sub-task was associated with a specific parent by verifying the hash chain.

A Concrete Example

Here is a complete Action grain recording an API search, expressed in JSON before field compaction and binary serialization:

{
  "type": "action",
  "tool_name": "web_search",
  "input": {
    "query": "Open Memory Specification agent memory",
    "max_results": 10,
    "language": "en"
  },
  "content": {
    "results": [
      {
        "title": "OMS v1.2 Specification",
        "url": "https://memorygrain.org/spec",
        "snippet": "The Open Memory Specification defines the .mg container..."
      }
    ],
    "total_count": 42
  },
  "is_error": false,
  "duration_ms": 340,
  "created_at": 1737000000000,
  "namespace": "research",
  "author_did": "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK",
  "structural_tags": ["api-call", "web-search"]
}

After field compaction, the payload keys become their short forms (t, tn, inp, cnt, iserr, dur, ca, ns, adid, tags), the map is sorted lexicographically, and the whole thing is serialized as canonical MessagePack. The 9-byte header is prepended: version 0x01, flags byte, type byte 0x05 (Action), the first two bytes of SHA-256("research") as the namespace hash, and the created_at epoch seconds as a 4-byte big-endian uint32. Finally, SHA-256 is computed over the complete blob to produce the content address.

The result is a single, self-contained binary object that can be stored, transmitted, verified, and referenced by hash.

The Immutable Audit Trail

The fundamental property that makes Action grains valuable for auditing is immutability. Each tool invocation creates a new grain. That grain is never modified — its content address is a SHA-256 hash of its bytes, and any change would produce a different hash.

This means:

  • No after-the-fact editing. An agent cannot retroactively change what input it passed to a tool or what content it received.
  • No silent deletion. While grains can be marked as superseded (via the superseded_by field), the original grain's bytes remain in storage, and its content address continues to resolve.
  • Cryptographic verification. When an Action grain is wrapped in a COSE Sign1 envelope (indicated by the signed flag in byte 1 of the header), a third party can verify both the integrity of the record and the identity of the agent that created it.

This produces a chain of actions where every link is individually verifiable. If an agent claims it called a particular API with particular input, the Action grain either exists with a valid content address or it does not.

Industry Use Cases

API Call Auditing

Every external API call an agent makes can be recorded as an Action grain. This creates a complete audit trail: which APIs were called, with what parameters, what they returned, when, and by which agent. For organizations that need to demonstrate to regulators or auditors what their AI systems did and why, this is foundational.

The author_did field ties each action to a specific agent identity via a W3C Decentralized Identifier. The namespace field partitions actions by project, team, or environment. The structural_tags field enables classification — tagging actions as "production", "pii-access", or "billing" for targeted auditing.

Debugging Agent Failures

When an agent fails, the most valuable debugging information is the exact sequence of actions leading up to the failure. With Action grains, you can:

  1. Filter grains by author_did and time range to isolate the agent's activity
  2. Reconstruct the full execution trace via parent_task_id references
  3. Inspect the exact input that was passed — not a summary, not a log approximation, but the actual serialized map
  4. Examine the content and error_type fields to see what the tool returned or why it failed
  5. Replay the sequence by feeding the same input to the same tools

Because Action grains store the actual input map and the actual content (any MessagePack-serializable value), replay is exact. There is no information loss between what happened and what was recorded.

Cost Tracking and Billing

The duration_ms field enables straightforward cost attribution. If your organization bills for agent compute time or tracks API usage costs, you can aggregate duration_ms across Action grains filtered by namespace, agent DID, or time range.

Combined with structural_tags, you can build cost breakdowns by category: how much time was spent on search actions versus database queries versus LLM inference. The immutability of grains means these numbers cannot be retroactively adjusted — they are what they are.

Compliance Logging

Regulated industries need to prove what actions an AI agent took. Healthcare organizations subject to HIPAA, financial institutions under SOX, and any organization processing personal data under GDPR need verifiable records of automated actions.

Action grains provide this by design:

  • Each action is individually content-addressed and optionally signed
  • The user_id field links actions to data subjects for GDPR right-of-access requests
  • The namespace field enables partitioning by compliance domain
  • The OMS sensitivity classification (bits 6-7 of the flags byte) can mark Action grains as pii (binary 10) or phi (binary 11) at the header level, enabling O(1) filtering without payload deserialization

Agent Benchmarking

Action grains are a natural fit for measuring agent performance. Given a corpus of Action grains from an agent's operation, you can compute:

  • Success rates: Aggregate the is_error field across tools, time ranges, or namespaces (remember: is_error: false means success)
  • Latency distributions: Analyze duration_ms to compute p50, p95, p99 latencies per tool
  • Error patterns: Group failed actions by error_type to identify systemic issues
  • Tool usage frequency: Count grains per tool_name to understand agent behavior

Because every Action is a separate, immutable grain, benchmarking data cannot be cherry-picked or retroactively filtered. The content-addressed nature means you can prove that a benchmark dataset has not been tampered with — the set of content addresses constitutes a verifiable manifest.

Action and the Broader Memory Graph

Action grains do not exist in isolation. They participate in the broader OMS memory graph through several mechanisms:

  • Goal satisfaction evidence. A Goal grain's satisfaction_evidence field is an array of content addresses pointing to Action, Belief, or Observation grains that substantiate a satisfied state transition. This creates a verifiable link between "what the agent was trying to achieve" and "what the agent actually did."
  • Rollback on failure. A Goal grain's rollback_on_failure field can reference Action or Workflow grains to execute when a goal fails. This connects failure handling to the specific compensating actions taken.
  • Provenance chains. Other grains can reference Action grains in their derived_from arrays, indicating that a Belief or Event was produced as a consequence of a tool invocation.
  • Cross-links. The related_to field (Section 14) enables arbitrary semantic links between Action grains and other grains — "caused_by", "led_to", "related_to" relationship types.

Putting It Together

The Action grain type transforms ephemeral tool invocations into durable, verifiable knowledge. Each action becomes a first-class citizen of agent memory — addressable by hash, filterable by header byte, linkable to goals and tasks, and provable through cryptographic signatures.

For teams building agent systems, adopting Action grains means never having to reconstruct what an agent did from scattered logs. The audit trail is built in, one grain at a time. For the complete Action schema, field compaction keys, and serialization rules, see Section 8.5 of the OMS v1.2 specification.