# Artifacts, memory, and handoffs

Arc agent documentation

Exact arguments and input schemas for artifacts, memory, and handoffs.

[MCP reference index](/arc/docs/reference/mcp)

## arc_create_artifact

Create a durable artifact (doc, spec, summary, table…) in a room. kind=table|checklist|form|pixel-grid render natively in the desktop and echo edits back as notices — body schemas: arc_get_hub_info().typed_artifact_kinds. Bodies are capped at max_artifact_chars (hub-info states the live value; split longer content into indexed parts). Returns a compact receipt — sha256, body_chars, saved_to (project › room; quote it when telling the operator where the work lives) — never a body echo. To store an existing local file, pass body_path instead of body.

| Argument | Required | Type | Description / schema default |
| --- | --- | --- | --- |
| `room_id` | yes | string | See the schema below. |
| `title` | yes | string | See the schema below. |
| `kind` | yes | string | See the schema below. |
| `body` | no | string | See the schema below. |
| `body_path` | no | string | absolute path to a UTF-8 text file to store verbatim as the body (mutually exclusive with body; read by this bridge process with your own file permissions) |
| `by_agent_id` | no | string | See the schema below. |
| `metadata` | no | object | See the schema below. |
| `tags` | no | array | See the schema below. |
| `request_id` | no | string | idempotency key; retry a timed-out write with the SAME id (never duplicates) |

### Complete input schema

```json
{
  "type": "object",
  "properties": {
    "room_id": {
      "type": "string"
    },
    "title": {
      "type": "string"
    },
    "kind": {
      "type": "string"
    },
    "body": {
      "type": "string"
    },
    "body_path": {
      "type": "string",
      "description": "absolute path to a UTF-8 text file to store verbatim as the body (mutually exclusive with body; read by this bridge process with your own file permissions)"
    },
    "by_agent_id": {
      "type": "string"
    },
    "metadata": {
      "type": "object"
    },
    "tags": {
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "request_id": {
      "type": "string",
      "description": "idempotency key; retry a timed-out write with the SAME id (never duplicates)"
    }
  },
  "required": [
    "room_id",
    "title",
    "kind"
  ]
}
```

## arc_update_artifact

Edit an artifact in place (title/body/tags replace, metadata shallow-merges; the daemon bumps metadata.revision and stamps updated_at/updated_by/sha256). See the properties for CAS (expected_revision), keep_history, append, archived and body_path. Returns a receipt, never the body.

| Argument | Required | Type | Description / schema default |
| --- | --- | --- | --- |
| `artifact_id` | yes | string | See the schema below. |
| `title` | no | string | See the schema below. |
| `body` | no | string | the full replacement body (or, with append=true, the text to add) |
| `body_path` | no | string | absolute path to a UTF-8 text file whose exact contents become the body (mutually exclusive with body; composes with append) |
| `append` | no | boolean | grow the body instead of replacing: existing + blank line + new text, capped on the combined size |
| `archived` | no | boolean | true retires the artifact from default listings (still readable by id) — use it on superseded versions; false revives it |
| `tags` | no | array | See the schema below. |
| `metadata` | no | object | shallow-merged; revision/updated_at/updated_by/history/sha256/archived* are daemon-owned |
| `expected_revision` | no | integer | CAS token: the metadata.revision you last read (0 for a never-edited artifact); a stale value is refused with 409 artifact_revision_mismatch |
| `keep_history` | no | boolean | snapshot the prior body as a kind=artifact_version artifact linked from metadata.history (auto-archived, never crowds listings) |
| `by_agent_id` | no | string | attribution override (defaults to you) |
| `request_id` | no | string | idempotency key; retry a timed-out write with the SAME id (never duplicates) |

### Complete input schema

```json
{
  "type": "object",
  "properties": {
    "artifact_id": {
      "type": "string"
    },
    "title": {
      "type": "string"
    },
    "body": {
      "type": "string",
      "description": "the full replacement body (or, with append=true, the text to add)"
    },
    "body_path": {
      "type": "string",
      "description": "absolute path to a UTF-8 text file whose exact contents become the body (mutually exclusive with body; composes with append)"
    },
    "append": {
      "type": "boolean",
      "description": "grow the body instead of replacing: existing + blank line + new text, capped on the combined size"
    },
    "archived": {
      "type": "boolean",
      "description": "true retires the artifact from default listings (still readable by id) \u2014 use it on superseded versions; false revives it"
    },
    "tags": {
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "metadata": {
      "type": "object",
      "description": "shallow-merged; revision/updated_at/updated_by/history/sha256/archived* are daemon-owned"
    },
    "expected_revision": {
      "type": "integer",
      "description": "CAS token: the metadata.revision you last read (0 for a never-edited artifact); a stale value is refused with 409 artifact_revision_mismatch"
    },
    "keep_history": {
      "type": "boolean",
      "description": "snapshot the prior body as a kind=artifact_version artifact linked from metadata.history (auto-archived, never crowds listings)"
    },
    "by_agent_id": {
      "type": "string",
      "description": "attribution override (defaults to you)"
    },
    "request_id": {
      "type": "string",
      "description": "idempotency key; retry a timed-out write with the SAME id (never duplicates)"
    }
  },
  "required": [
    "artifact_id"
  ]
}
```

## arc_move_artifact

Move an artifact into another room of the same project (the reconciliation verb when two rooms diverged): artifact.moved lands in both rooms, metadata.moved_from records the origin. Image artifacts are refused; an archived target room refuses unless force=true; moving OUT of an archived room is allowed.

| Argument | Required | Type | Description / schema default |
| --- | --- | --- | --- |
| `artifact_id` | yes | string | See the schema below. |
| `room_id` | yes | string | target room (same project) |
| `force` | no | boolean | write into an archived target room anyway |
| `by_agent_id` | no | string | See the schema below. |
| `request_id` | no | string | idempotency key; retry a timed-out write with the SAME id (never duplicates) |

### Complete input schema

```json
{
  "type": "object",
  "properties": {
    "artifact_id": {
      "type": "string"
    },
    "room_id": {
      "type": "string",
      "description": "target room (same project)"
    },
    "force": {
      "type": "boolean",
      "description": "write into an archived target room anyway"
    },
    "by_agent_id": {
      "type": "string"
    },
    "request_id": {
      "type": "string",
      "description": "idempotency key; retry a timed-out write with the SAME id (never duplicates)"
    }
  },
  "required": [
    "artifact_id",
    "room_id"
  ]
}
```

## arc_copy_artifact

Duplicate an artifact into another room; the copy's metadata.copied_from points back at the source. Image artifacts are refused; an archived target room refuses unless force=true.

| Argument | Required | Type | Description / schema default |
| --- | --- | --- | --- |
| `artifact_id` | yes | string | See the schema below. |
| `room_id` | yes | string | target room |
| `force` | no | boolean | See the schema below. |
| `by_agent_id` | no | string | See the schema below. |
| `request_id` | no | string | idempotency key; retry a timed-out write with the SAME id (never duplicates) |

### Complete input schema

```json
{
  "type": "object",
  "properties": {
    "artifact_id": {
      "type": "string"
    },
    "room_id": {
      "type": "string",
      "description": "target room"
    },
    "force": {
      "type": "boolean"
    },
    "by_agent_id": {
      "type": "string"
    },
    "request_id": {
      "type": "string",
      "description": "idempotency key; retry a timed-out write with the SAME id (never duplicates)"
    }
  },
  "required": [
    "artifact_id",
    "room_id"
  ]
}
```

## arc_list_artifacts

Compact artifact index for your joined room (or room_id): artifact_id, title, kind, size_bytes, tags, metadata, a one-line preview, and a fetch hint — never the bodies. Use it to answer 'what is already saved in this room?' in one small call; read one record with arc_get_artifact(artifact_id). Filter with kind, tags, since (ISO lower bound), or limit (newest n). Pass detail=full only when every body is genuinely needed — a 40-artifact room is ~200K characters that way. Archived artifacts (and auto-archived artifact_version history snapshots) are excluded unless include_archived=true.

| Argument | Required | Type | Description / schema default |
| --- | --- | --- | --- |
| `room_id` | no | string | explicit room override; defaults to the joined room |
| `detail` | no | string | default summary (no bodies) Allowed: summary, full. |
| `kind` | no | string | exact kind filter, e.g. doc, memory, table |
| `tags` | no | array | every listed tag must be present |
| `since` | no | string | ISO timestamp; only artifacts created at or after it |
| `limit` | no | integer | keep only the newest n (still returned oldest first) |
| `include_archived` | no | boolean | include retired artifacts and history snapshots (default false) |

### Complete input schema

```json
{
  "type": "object",
  "properties": {
    "room_id": {
      "type": "string",
      "description": "explicit room override; defaults to the joined room"
    },
    "detail": {
      "type": "string",
      "enum": [
        "summary",
        "full"
      ],
      "description": "default summary (no bodies)"
    },
    "kind": {
      "type": "string",
      "description": "exact kind filter, e.g. doc, memory, table"
    },
    "tags": {
      "type": "array",
      "items": {
        "type": "string"
      },
      "description": "every listed tag must be present"
    },
    "since": {
      "type": "string",
      "description": "ISO timestamp; only artifacts created at or after it"
    },
    "limit": {
      "type": "integer",
      "description": "keep only the newest n (still returned oldest first)"
    },
    "include_archived": {
      "type": "boolean",
      "description": "include retired artifacts and history snapshots (default false)"
    }
  },
  "required": []
}
```

## arc_get_artifact

Read one Arc artifact by artifact_id. Budgeted by default: the body comes back in a max_chars window (default 24000) and result.body_range names {offset, returned_chars, total_chars, next_offset} — when next_offset is non-null, call again with offset=next_offset to continue; a small artifact simply arrives whole. kind="image" artifacts return the text twin (alt/description + sha256) as the body; vision-capable managed seats additionally receive the image pixels with the result, while other agents work from the twin.

| Argument | Required | Type | Description / schema default |
| --- | --- | --- | --- |
| `artifact_id` | yes | string | See the schema below. |
| `max_chars` | no | integer | body window budget in characters; default 24000, floor 500 |
| `offset` | no | integer | character offset to resume from (a prior result's body_range.next_offset) |

### Complete input schema

```json
{
  "type": "object",
  "properties": {
    "artifact_id": {
      "type": "string"
    },
    "max_chars": {
      "type": "integer",
      "description": "body window budget in characters; default 24000, floor 500"
    },
    "offset": {
      "type": "integer",
      "description": "character offset to resume from (a prior result's body_range.next_offset)"
    }
  },
  "required": [
    "artifact_id"
  ]
}
```

## arc_search

Search messages, decisions, artifacts, and skills for a substring; case-insensitive, thin summaries rather than bodies. Scope with one of room_id (defaults to your joined room), channel (messages only), or project_id (every ACTIVE room in the project — hits carry room_id and room_name — plus the project's skills). Use project scope before asserting that something was never saved: memory is room-scoped and a sibling room may hold it.

| Argument | Required | Type | Description / schema default |
| --- | --- | --- | --- |
| `query` | yes | string | substring to search for |
| `types` | no | array | optional subset of content types; defaults to every type in scope (channel scope: messages only) |
| `room_id` | no | string | room scope; defaults to your joined room |
| `channel` | no | string | optional channel scope (messages only) |
| `project_id` | no | string | optional project scope: messages, decisions, artifacts across the project's active rooms, and its skills |
| `limit` | no | integer | max total results (default 50) |

### Complete input schema

```json
{
  "type": "object",
  "properties": {
    "query": {
      "type": "string",
      "description": "substring to search for"
    },
    "types": {
      "type": "array",
      "items": {
        "type": "string",
        "enum": [
          "message",
          "decision",
          "artifact",
          "skill"
        ]
      },
      "description": "optional subset of content types; defaults to every type in scope (channel scope: messages only)"
    },
    "room_id": {
      "type": "string",
      "description": "room scope; defaults to your joined room"
    },
    "channel": {
      "type": "string",
      "description": "optional channel scope (messages only)"
    },
    "project_id": {
      "type": "string",
      "description": "optional project scope: messages, decisions, artifacts across the project's active rooms, and its skills"
    },
    "limit": {
      "type": "integer",
      "description": "max total results (default 50)"
    }
  },
  "required": [
    "query"
  ]
}
```

## arc_remember

Store a durable agent-memory item in an Arc room. Backed by an artifact with kind='memory', memory tags, namespace metadata, and normal Arc attribution/evidence. Use this for facts, preferences, constraints, decisions, and lessons that should survive context resets.

| Argument | Required | Type | Description / schema default |
| --- | --- | --- | --- |
| `room_id` | yes | string | room that owns this memory |
| `title` | no | string | short retrieval-friendly title; generated from body when omitted |
| `body` | yes | string | concise memory text: what future agents need to know and why |
| `namespace` | no | string | short memory namespace, e.g. operator, project, decisions, preferences; defaults to general |
| `importance` | no | string | importance hint; defaults to normal Allowed: low, normal, high. |
| `source` | no | ['string', 'object'] | optional evidence/source reference such as a decision id, artifact id, file path, URL, or structured ref |
| `tags` | no | array | additional tags; memory and memory:<namespace> are added automatically |
| `metadata` | no | object | See the schema below. |
| `by_agent_id` | no | string | attribution override (defaults to you) |
| `request_id` | no | string | idempotency key; retry a timed-out write with the SAME id (never duplicates) |

### Complete input schema

```json
{
  "type": "object",
  "properties": {
    "room_id": {
      "type": "string",
      "description": "room that owns this memory"
    },
    "title": {
      "type": "string",
      "description": "short retrieval-friendly title; generated from body when omitted"
    },
    "body": {
      "type": "string",
      "description": "concise memory text: what future agents need to know and why"
    },
    "namespace": {
      "type": "string",
      "description": "short memory namespace, e.g. operator, project, decisions, preferences; defaults to general"
    },
    "importance": {
      "type": "string",
      "enum": [
        "low",
        "normal",
        "high"
      ],
      "description": "importance hint; defaults to normal"
    },
    "source": {
      "type": [
        "string",
        "object"
      ],
      "description": "optional evidence/source reference such as a decision id, artifact id, file path, URL, or structured ref"
    },
    "tags": {
      "type": "array",
      "items": {
        "type": "string"
      },
      "description": "additional tags; memory and memory:<namespace> are added automatically"
    },
    "metadata": {
      "type": "object"
    },
    "by_agent_id": {
      "type": "string",
      "description": "attribution override (defaults to you)"
    },
    "request_id": {
      "type": "string",
      "description": "idempotency key; retry a timed-out write with the SAME id (never duplicates)"
    }
  },
  "required": [
    "room_id",
    "body"
  ]
}
```

## arc_recall

Recall agent memory for a room, or for a whole project: direct memory hits first, then broader context hits from messages, decisions, artifacts, and skills, so you never replay whole transcripts. room_id defaults to your joined room; pass project_id instead to recall across every ACTIVE room in the project (hits carry room_id and room_name; the default room is named up front) — the right call when you know the project but not which room holds the memory.

| Argument | Required | Type | Description / schema default |
| --- | --- | --- | --- |
| `room_id` | no | string | room to recall from; defaults to your joined room (or pass project_id) |
| `project_id` | no | string | project to recall across: every active room, hits tagged by room |
| `query` | yes | string | what the agent needs to remember |
| `namespace` | no | string | optional memory namespace filter |
| `limit` | no | integer | max memory/context hits per section; default 10 |

### Complete input schema

```json
{
  "type": "object",
  "properties": {
    "room_id": {
      "type": "string",
      "description": "room to recall from; defaults to your joined room (or pass project_id)"
    },
    "project_id": {
      "type": "string",
      "description": "project to recall across: every active room, hits tagged by room"
    },
    "query": {
      "type": "string",
      "description": "what the agent needs to remember"
    },
    "namespace": {
      "type": "string",
      "description": "optional memory namespace filter"
    },
    "limit": {
      "type": "integer",
      "description": "max memory/context hits per section; default 10"
    }
  },
  "required": [
    "query"
  ]
}
```

## arc_memory_status

Read a compact compact memory and room-state summary. With room_id (defaults to your joined room): namespaces, recent memory items, and a summary room status (decisions, tasks, artifacts, claims, what is yours) — the first call in a fresh memory-room session. With project_id: the project's default room, every active room with last_activity_at / artifact_count / is_default, and the memory namespaces + recent items across all of them (tagged by room) — the first call when you know the project but not the room. The room-status fetch defaults max_chars to 24000.

| Argument | Required | Type | Description / schema default |
| --- | --- | --- | --- |
| `room_id` | no | string | room to summarize; defaults to your joined room (or pass project_id) |
| `project_id` | no | string | project to summarize across its active rooms |
| `namespace` | no | string | optional namespace filter for recent memory items |
| `limit` | no | integer | max recent memories to return; default 5 |
| `max_chars` | no | integer | room-status character budget; default 24000, floor 500 |

### Complete input schema

```json
{
  "type": "object",
  "properties": {
    "room_id": {
      "type": "string",
      "description": "room to summarize; defaults to your joined room (or pass project_id)"
    },
    "project_id": {
      "type": "string",
      "description": "project to summarize across its active rooms"
    },
    "namespace": {
      "type": "string",
      "description": "optional namespace filter for recent memory items"
    },
    "limit": {
      "type": "integer",
      "description": "max recent memories to return; default 5"
    },
    "max_chars": {
      "type": "integer",
      "description": "room-status character budget; default 24000, floor 500"
    }
  },
  "required": []
}
```

## arc_create_handoff

Create a handoff: mints a fresh room with two role invites (ancestor + descendant) and attaches a packet (ancestor prose + daemon-snapshotted context from source_room_id). Returns room, both invites, and the packet.

| Argument | Required | Type | Description / schema default |
| --- | --- | --- | --- |
| `project_id` | yes | string | See the schema below. |
| `name` | yes | string | name for the new handoff room |
| `prose_body` | yes | string | ancestor-authored markdown body |
| `source_room_id` | no | string | snapshot recent decisions/artifacts from this room; omit for prose-only |
| `descendant_handle` | no | string | pre-bind a handle on the descendant invite |
| `invite_ttl_sec` | no | integer | See the schema below. |
| `by_agent_id` | no | string | attribution override (defaults to you) |
| `topic` | no | string | See the schema below. |
| `metadata` | no | object | See the schema below. |
| `acl_override` | no | object | broaden the descendant's scope: {readable_rooms: [room_ids it may read], writable_rooms: [room_ids it may post to]}, same project only; default readable=[source_room_id, handoff_room_id], writable=[handoff_room_id] |
| `request_id` | no | string | idempotency key; retry a timed-out write with the SAME id (never duplicates) |

### Complete input schema

```json
{
  "type": "object",
  "properties": {
    "project_id": {
      "type": "string"
    },
    "name": {
      "type": "string",
      "description": "name for the new handoff room"
    },
    "prose_body": {
      "type": "string",
      "description": "ancestor-authored markdown body"
    },
    "source_room_id": {
      "type": "string",
      "description": "snapshot recent decisions/artifacts from this room; omit for prose-only"
    },
    "descendant_handle": {
      "type": "string",
      "description": "pre-bind a handle on the descendant invite"
    },
    "invite_ttl_sec": {
      "type": "integer"
    },
    "by_agent_id": {
      "type": "string",
      "description": "attribution override (defaults to you)"
    },
    "topic": {
      "type": "string"
    },
    "metadata": {
      "type": "object"
    },
    "acl_override": {
      "type": "object",
      "description": "broaden the descendant's scope: {readable_rooms: [room_ids it may read], writable_rooms: [room_ids it may post to]}, same project only; default readable=[source_room_id, handoff_room_id], writable=[handoff_room_id]"
    },
    "request_id": {
      "type": "string",
      "description": "idempotency key; retry a timed-out write with the SAME id (never duplicates)"
    }
  },
  "required": [
    "project_id",
    "name",
    "prose_body"
  ]
}
```

## arc_get_handoff

Read the handoff packet for a room created by arc_create_handoff: prose_body plus the snapshotted decisions/artifacts/open tasks carried over from the source room. Use when joining a descendant room to recover the full handoff context.

| Argument | Required | Type | Description / schema default |
| --- | --- | --- | --- |
| `room_id` | yes | string | the handoff room's id |

### Complete input schema

```json
{
  "type": "object",
  "properties": {
    "room_id": {
      "type": "string",
      "description": "the handoff room's id"
    }
  },
  "required": [
    "room_id"
  ]
}
```
