Skip to main content

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:

  1. A person saves a GitHub token in MemoryRelay.
  2. A person with admin connects the repository, branch and folder.
  3. MemoryRelay syncs the folder. If it is not ICM-ready yet, a person makes it ready from a starter.
  4. Changes are made in the workspace's draft (by people in the app, or by agents), checked, and proposed as a pull request.
  5. 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 MemoryRelayRepository permissions
Read the folder (sync)Contents: Read-only
Also open and update pull requestsContents: 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}'
  • ref is the branch MemoryRelay reads and publishes from; pull requests target it.
  • path is the folder inside the repository (empty for the root).
  • auto_sync syncs every 15 minutes; otherwise a person syncs with POST .../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[].codeMeaning
source_manifest_missingThe folder has files but no icm.source.json: it is not ICM-ready yet
source_emptyThe folder does not exist or holds no files: not ICM-ready yet
ref_not_foundThe branch does not exist (the message names the default branch)
source_unavailableThe repository cannot be read with this token
source_root_mismatchThe 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"
FieldWhat 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
suggestionstemplates 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.

ModeWhat happens
use_existingThe repository already has a workspace: the source points at it (following a workspace_root its source file declares) and syncs. Nothing is written
adoptThe 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
starterA 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:

  1. POST .../draft/check compiles and walks the folder as the sync will read it after the merge, and returns the draft's digest. Adding or removing a file also proposes an updated icm.source.json; a file the source file does not name is reported as a warning because the sync will not read it.
  2. POST .../draft/pull-request with 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_propose opens or updates the pull request. An agent key may do this only when it is confined to the workspace with icm:run and a person with admin turned on agent_proposals for the source. Otherwise the call is refused with agent_proposals_off before anything is written, and the refusal is audited.
  • On a service workspace, icm_draft_propose sends 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​

CodeWhat to do
token_cannot_writeThe 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_missingThe source has no token; a person attaches one
agent_proposals_offA person opens the pull request, or a person with admin turns on agent proposals
key_not_confinedUse a key created for this workspace (confined)
draft_changedCheck the draft again and propose the new digest
branch_movedThe proposal branch has commits MemoryRelay did not make; close that pull request and propose again
symlink_refusedThe draft writes over a symlink or submodule in the repository; MemoryRelay never does
source_missingConfigure the workspace's repository first
workspace_not_foundThere is no ICM workspace in that folder at this commit; read the repository again
insufficient_roleChanging the folder or source file needs admin