---
title: memhtml session put
description: "Append write ops, and label or unlabel ops on existing records, to a session's overlay from a JSONL file."
---

## 1. Synopsis

Append write ops, and label or unlabel ops on existing records, to a session's overlay from a JSONL file.

```sh
memhtml session put --file <string> [options]
```

## 2. Arguments

This command takes no positional arguments.

## 3. Flags

| Flag | Type | Default | Values | Description |
| --- | --- | --- | --- | --- |
| `--id` | string | *no default* | *no fixed set* | The session to append to. Defaults to $MEMHTML_SESSION; with neither, the call is ERR_MISSING_ARGUMENT. With the id taken from $MEMHTML_SESSION and no log yet, the session is started on refs/heads/main's tip first (one `rev-parse`, no head load), so a run's first write needs no `session start`; a `--id` given on the line must name a started session. |
| `--file` | string | *no default* | *no fixed set* | JSONL, one object per line. A `write` line takes the same fields `memhtml apply` accepts; each is rendered to the file the store would write and lands at the path the store would choose. A `label` line, `{"op":"label","path":"areas/inbox/x.html","entity":"system:memhtml"}`, adds one `memhtml-entity` to an existing record's head, and an `unlabel` line with the same fields removes one; neither touches the article, so the record keeps its content hash. A label value is a lowercase `type:name` (`service:memhtml`, `project:agent-runtime`, `person:laura`) the record does not carry yet; an unlabel names a value the record carries, exactly as written. `-` reads stdin. A malformed op, a reserved path, a label that is malformed or already carried, an unlabel of a value the record lacks, an unlabel that leaves a record with no entity outside `projects/<slug>/` while the store requires an anchor, or a record below the write bar refuses the whole call and appends nothing. The bar is the store's policy, `writeBar` in `memhtml.config.json` at the store root (`memhtml config` shows the effective values): `refusedTypes` names the types no writer may put, each refused with the reason the file gives, and `requireAnchor` makes every record name the system, project, or person it is about (an `entities` value, or a `workspace`; a task is anchored by either). With no file the bar refuses no type and requires no anchor; the format and integrity refusals hold either way, and a file that does not decode refuses the call with ERR_STORE_CONFIG. The bar holds for every writer and every type, the curator and tasks included. The response lists each put's nearest existing records under `neighbors`, so a near-duplicate can be skipped and the related records linked before the commit. `neighbors` is ranked the way `head search` ranks, the vector arm included when the store has a vector cache (`memhtml head embed`) and an embedder is configured: each put's own text is embedded as a document, one call per batch. When the arm cannot run the neighbors come from the lexical arm alone and `vector` says why (`used: false` with a `reason`); the put itself never fails for it. **Required.** |
| `--server` | flag | `true` | *no fixed set* | Ask the head server on .memhtml/head.sock first (`memhtml head serve`), falling back to loading the head here when no server accepts within 100 ms or answers within 10 s; `--no-server` always loads here. The placements, violations, and neighbors (label lines included, the vector arm run with the server's cache and embedder) are then computed by the server over the version at the session's base; the append always happens here. The payload's `head.source` is `server` when the server answered. |
| `--dry-run` | flag | `false` | *no fixed set* | Answer as the put would and append nothing: the same `paths`, `placements` (each put's `path` and the collision `ordinal` it took: 1 unsuffixed, 2 for `-2`, null for an explicit `path` used as given), neighbors, and violations, and the same refusal when a violation blocks, with `dryRun: true`, `appended: 0`, and the log untouched. Under $MEMHTML_SESSION with no log yet, no session is started: the answer is over the base that start would take, with an empty log. The answer holds for the session's base and log as they are; a put that lands first can take a path. |

Every command also accepts the [global flags](/reference/global-flags/).

## 4. Response

On success the command writes one JSON envelope to stdout, and its `type` is `session.appended`.

On failure it writes the failure envelope, whose `code` comes from [error codes](/reference/error-codes/).

[The JSON envelope](/reference/envelope/) gives the fields both shapes carry. [Response types](/reference/response-types/) lists every value `type` takes across the binary.

## 5. Further reading

The guide blocks that name this command:

- [`write-surfaces`](/reference/guide/write-surfaces/)

## 6. Provenance

A loader generates this page from `apps/cli/src/commands.ts` while the site builds, so no file in the repository holds it: the `COMMANDS` array there is the one source of argument parsing, of `memhtml manifest`, and of `AGENTS.md`. Change the registry and this page changes with it.