Work on a Git workspace
A Git workspace keeps its files in a GitHub repository. The repository is the
source of truth: MemoryRelay reads a folder of it, and every change reaches
that folder as a pull request. People decide; agents propose. The decision
is the merge, made in GitHub by a person. MemoryRelay never merges, never
pushes to the branch it reads or to the repository's default branch, and
writes only to branches it creates under memoryrelay/.
The flow:
- A person saves a GitHub token in MemoryRelay.
- A person with admin connects the repository, branch and folder.
- MemoryRelay syncs the folder. If it is not ICM-ready yet, a person makes it ready from a starter.
- Changes are made in the workspace's draft (by people in the app, or by agents), checked, and proposed as a pull request.
- A person merges the pull request in GitHub. MemoryRelay syncs the merged commit and publishes it.
1. Save a GitHub token
Create a fine-grained personal access token for only the one repository, then add it in the app (Settings, GitHub access) or with the API:
curl -X POST https://api.memoryrelay.net/v2/icm/source-credentials \
-H "Authorization: Bearer $SESSION" -H "Content-Type: application/json" \
-d '{"label": "factory repo", "token": "github_pat_..."}'
| To let MemoryRelay | Repository permissions |
|---|---|
| Read the folder (sync) | Contents: Read-only |
| Also open and update pull requests | Contents: Read and write, and Pull requests: Read and write |
Metadata: Read-only is added by GitHub automatically. A public repository can be read without a token; writing always needs one.
The token is encrypted at rest and never shown again: responses carry only
token_set. It is used only on the server, only toward api.github.com,
and only its owner can attach it to a source. Agents never see it. Saving,
rotating and deleting tokens are people-only; API keys are refused.
2. Connect the repository
A person with admin on the workspace sets the source:
curl -X PUT https://api.memoryrelay.net/v2/icm/workspaces/$WS/source \
-H "Authorization: Bearer $SESSION" -H "Content-Type: application/json" \
-d '{"repository": "acme/factory", "ref": "main", "path": "icm",
"credential_id": "'$CREDENTIAL'", "auto_sync": true, "agent_proposals": false}'
refis the branch MemoryRelay reads and publishes from; pull requests target it.pathis the folder inside the repository (empty for the root).auto_syncsyncs every 15 minutes; otherwise a person syncs withPOST .../source/sync.agent_proposals(off by default) lets an agent key open pull requests; see Agents.
A change to the source names the revision it replaces (If-Match, from the
ETag of GET .../source).
3. Sync, and "not ICM-ready yet"
A sync reads the folder at the branch's current commit, compiles and checks it,
and publishes it only if it passes. The folder must hold an icm.source.json
(or a release export's manifest.json) naming the files that make up the
workspace. A failed sync is recorded and nothing live changes.
last_sync.errors[].code | Meaning |
|---|---|
source_manifest_missing | The folder has files but no icm.source.json: it is not ICM-ready yet |
source_empty | The folder does not exist or holds no files: not ICM-ready yet |
ref_not_found | The branch does not exist (the message names the default branch) |
source_unavailable | The repository cannot be read with this token |
source_root_mismatch | The source file says its workspace root is another folder (workspace_root) |
When the folder is not a workspace, the error also carries found (other
folders of the same commit that are) and scan: what the repository holds.
What's in this repository
The Source page reads the names in the repository (never the contents) and shows what it holds: ICM workspaces it already has, the instruction files its agents already read, and the template that fits it best.
curl https://api.memoryrelay.net/v2/icm/workspaces/$WS/source/scan \
-H "Authorization: Bearer $SESSION"
| Field | What it holds |
|---|---|
workspaces | [{path, manifest}]: folders with icm.source.json (or an export's manifest.json next to its entry), at most 10, 4 deep |
instructions | [{path, kind}]: CLAUDE.md, AGENTS.md, GEMINI.md up to 3 deep; .github/copilot-instructions.md, .github/instructions/*.instructions.md, .github/prompts/*.prompt.md; .cursor/rules/*.mdc, .cursorrules, .windsurfrules; .claude/skills/*/SKILL.md, .claude/commands/*.md, .claude/agents/*.md; README.md, CONTRIBUTING.md, SECURITY.md, CHANGELOG.md, docs/README.md or docs/index.md |
suggestions | templates ranked by the files they fit, such as library.json |
recommended | {mode, path, reason}: use_existing, adopt or starter |
The scan comes from the one tree listing a sync already makes, cached per
commit. API keys need icm:context. Before a workspace exists,
GET /v2/icm/scan?repository=owner/name answers the same for a person (New
workspace uses it to suggest templates).
Where the source file lives
A source names a folder (path), which is the workspace root: node files and
stage paths start there. By default the source file is <path>/icm.source.json.
manifest names it elsewhere inside the folder, so the repository root can be
the workspace while ICM's own files stay under .github/icm/:
{"repository": "Alteriom/painlessMesh", "ref": "main", "path": "", "manifest": ".github/icm/icm.source.json"}
The release limits (2000 files, 1 MiB each, 8 MiB in all) apply to the files the source file names, not to everything in the folder. A pulled copy of such a workspace mirrors the repository: its paths are the repository's own.
4. Make it ICM-ready
Make it ICM-ready on the Source page offers three ways, the recommended one first, and lists the exact files the pull request would add or change before anything is written. It needs a person with publisher, and admin when it changes the folder or source file.
| Mode | What happens |
|---|---|
use_existing | The repository already has a workspace: the source points at it (following a workspace_root its source file declares) and syncs. Nothing is written |
adopt | The repository already instructs agents: the root becomes the workspace. Its CLAUDE.md is the entry and every instruction file found is named where it is, never copied. The template's pipelines, _shared/ and questionnaire go under a home folder (.github/icm, icm or .icm) with the source file there |
starter | A template's files and an icm.source.json go into a folder you choose |
curl -X POST https://api.memoryrelay.net/v2/icm/workspaces/$WS/source/make-ready \
-H "Authorization: Bearer $SESSION" -H "Content-Type: application/json" \
-d '{"mode": "adopt", "path": ".github/icm", "dry_run": true}'
The answer names source (path, manifest), files (each added or
modified), in_place (files named where they are), warnings and
conflicts. Without dry_run the files go into your draft, and you open the
pull request as in the next section.
Adopting appends a jobs table and "Route by what just happened" (with a
Route id column) to the existing CLAUDE.md; it never rewrites what is
there. If AGENTS.md differs from CLAUDE.md, choose agents: keep (it
stays as it is, outside the workspace), copy (it becomes CLAUDE.md's copy)
or pointer (a one-line pointer to CLAUDE.md). The plan warns when the entry
carries payload (tables of facts, a hand-maintained version) or an instruction
file is far over an L1 route's size; adopting only appends, so moving those is
yours to do.
POST .../draft/from-template still writes a starter into the configured
folder. Both need a source (source_missing) and are refused once the folder
already publishes (already_ready).
5. Change files and open a pull request
The draft of a Git workspace sits on the live version, which names the commit
it came from. Edit files in the app, or write them through the API
(PUT .../draft/files/{path}, POST .../draft/imports). Then:
POST .../draft/checkcompiles and walks the folder as the sync will read it after the merge, and returns the draft'sdigest. Adding or removing a file also proposes an updatedicm.source.json; a file the source file does not name is reported as a warning because the sync will not read it.POST .../draft/pull-requestwith the digest you reviewed:
curl -X POST https://api.memoryrelay.net/v2/icm/workspaces/$WS/draft/pull-request \
-H "Authorization: Bearer $SESSION" -H "Content-Type: application/json" \
-d '{"expected_digest": "'$DIGEST'", "title": "Clearer review rules"}'
{
"number": 12,
"url": "https://github.com/acme/factory/pull/12",
"branch": "memoryrelay/release-notes-1a2b3c4d",
"head_commit": "9f2c...",
"base_commit": "41d0...",
"state": "open",
"opened": true,
"pushed": true
}
The first call opens the pull request on a new memoryrelay/ branch. Later
calls push one new commit to the same branch and update its title and
description; the branch is only ever fast-forwarded. If someone pushed to that
branch in GitHub, MemoryRelay refuses (branch_moved) rather than
overwrite it. A draft that changed since you reviewed it is refused
(draft_changed). The description lists the changes and says who proposed it
through MemoryRelay. GET .../draft returns the pull request as proposal.
Publishing a Git draft directly (POST .../draft/publish) is refused with
authority_is_git: the merge is the publish.
6. After the merge
POST .../draft/pull-request/refresh (anyone who can read the workspace)
reads the pull request. When it is merged, MemoryRelay syncs the source and
clears the draft once the published commit contains the merge; when it is
closed, the draft stays and the next proposal opens a new pull request. The
15-minute job refreshes open pull requests on its own.
Agents
Agents use the same routes through MCP (the remote endpoint or the npm
package): icm_draft_get (changes, digest, proposal), icm_draft_write
(writes files into the draft), icm_draft_propose.
- On a Git workspace,
icm_draft_proposeopens or updates the pull request. An agent key may do this only when it is confined to the workspace withicm:runand a person with admin turned onagent_proposalsfor the source. Otherwise the call is refused withagent_proposals_offbefore anything is written, and the refusal is audited. - On a service workspace,
icm_draft_proposesends nothing and returns the address where a person reviews and publishes the draft. - No tool merges, publishes, or changes the source or its switch.
Errors
| Code | What to do |
|---|---|
token_cannot_write | The token can read but not write. Give it Contents: Read and write and Pull requests: Read and write on this repository (the response lists them in permissions), or attach one that has them |
credential_missing | The source has no token; a person attaches one |
agent_proposals_off | A person opens the pull request, or a person with admin turns on agent proposals |
key_not_confined | Use a key created for this workspace (confined) |
draft_changed | Check the draft again and propose the new digest |
branch_moved | The proposal branch has commits MemoryRelay did not make; close that pull request and propose again |
symlink_refused | The draft writes over a symlink or submodule in the repository; MemoryRelay never does |
source_missing | Configure the workspace's repository first |
workspace_not_found | There is no ICM workspace in that folder at this commit; read the repository again |
insufficient_role | Changing the folder or source file needs admin |