Workspace sync
An agent can back up its workspace — the files it works with and its saved
sessions — to Alfe and restore them later, for example onto a fresh machine.
These endpoints are what the agent’s built-in sync uses; you can call them
directly with the @alfe.ai/agent-api-client.
File bytes move to and from storage over short-lived presigned URLs (valid for 300 seconds). The agent API hands you the URL; you PUT or GET the bytes directly.
Register
Section titled “Register”Register the agent for workspace sync (safe to call repeatedly).
const { agent } = await client.syncRegister({ displayName: "My Agent" });// POST /agent/sync/register{ "data": { "agent": { "agentId": "agt_…", "displayName": "My Agent", "status": "synced", "fileCount": 42, "totalSize": 918240, "lastSync": "2026-07-01T12:00:00.000Z" } }}Read the manifest
Section titled “Read the manifest”The manifest lists every backed-up file with its content hash and size — compare it against local files to decide what changed.
const manifest = await client.syncGetManifest();// GET /agent/sync/manifest// { version, agentId, lastSync, files: { "<path>": { hash, size, modified } } }Upload: presign, then confirm
Section titled “Upload: presign, then confirm”Uploading is two steps. First ask for a presigned put URL for each file. Pass
the file’s size in bytes and its SHA-256 as 64 lowercase hex characters: the
service signs both, so storage rejects any body of another length or digest.
The response’s headers must be sent with the PUT exactly as given; a PUT
without them is rejected.
import { createHash } from "node:crypto";import { readFile } from "node:fs/promises";
const bytes = await readFile("notes/todo.md");const sha256 = createHash("sha256").update(bytes).digest("hex");
const { urls } = await client.syncPresign({ files: [ { path: "notes/todo.md", operation: "put", contentType: "text/markdown", size: bytes.byteLength, sha256, }, ],});// POST /agent/sync/presignPUT the bytes to the returned url within 5 minutes (the URL expires after
300 seconds). The upload lands in a staging area, not in your workspace. Then
confirm it: the service checks the staged object’s size and checksum against
what you declare, and only then publishes it to the workspace and the
manifest.
await fetch(urls[0].url, { method: "PUT", body: bytes, headers: { ...urls[0].headers, // the signed checksum header: required "Content-Type": "text/markdown", // sets the stored content type },});
await client.syncConfirmUpload({ filePath: "notes/todo.md", hash: `sha256:${sha256}`, // note the `sha256:` prefix size: bytes.byteLength, sha256, // the same bare hex you presigned with});// POST /agent/sync/confirmhash takes the sha256:<hex> form. Sending sha256 as well makes the
confirm fail closed: if storage recorded no matching checksum, the upload is
discarded with 409. Uploads that declared no checksum are hashed by the
service at confirm instead. A confirm whose size or checksum doesn’t match also
returns 409, and the staged bytes are discarded; presign again and retry.
Files are capped at 25 MiB.
Paths that never sync
Section titled “Paths that never sync”Secret-bearing files are refused with 409 and code: "PATH_FORBIDDEN", for
uploads and for reads (file content, presigned get URLs, restore bundles).
The match ignores letter case. The list covers .env files (except
.env.example, .env.sample and .env.template), private keys (*.pem,
*.key, *.p8, *.p12, *.pfx, id_rsa and similar, but not *.pub),
.ssh/, .aws/, .git/config, .git-credentials, .npmrc, .netrc,
cloud credential files, Terraform state, MCP server configs and
openclaw.json. In the agent’s runtime home it also covers credential,
identity and device directories and the per-agent auth store. The *.key
rule also blocks Keynote decks. Refused files still show up in listings and can
be deleted.
Restore
Section titled “Restore”Ask for a bundle of presigned get URLs to reconstruct the workspace. Choose how
much to restore with mode:
const bundle = await client.syncReconstruct({ mode: "full" });// POST /agent/sync/reconstruct// mode: "full" | "active" | "memory"Each entry in bundle.files has a path and a presigned url; GET each one to
pull the bytes down. The URLs expire after 300 seconds. Secret-bearing paths
are left out of the bundle.
List and inspect
Section titled “List and inspect”const { files } = await client.syncListFiles({ prefix: "notes/" });// GET /agent/sync/files?prefix=notes/
const stats = await client.syncGetStats();// GET /agent/sync/stats → { fileCount, lastSyncAt, ... }Sessions
Section titled “Sessions”Saved sessions are backed up too and can be listed and read back individually.
const { sessions } = await client.syncListSessions();// GET /agent/sync/sessions
const { content } = await client.syncGetSession(sessions[0].sessionId);// GET /agent/sync/sessions/{sessionId}Delete a file
Section titled “Delete a file”const { removed } = await client.syncDeleteFile("notes/todo.md");// DELETE /agent/sync/files/{filePath}Shared files vs synced files
Section titled “Shared files vs synced files”The endpoints above cover the agent’s own workspace. Files shared with the agent at the org, team, or project level are a separate, read-oriented surface — see Shared knowledge.