Your first project
The workspace on its own is a fine scratch space, but LatchAI gets interesting when you point it at a repository you already work in. That is what mounts are for.
Add a folder to the workspace
Section titled “Add a folder to the workspace”A mount is a folder from anywhere on your machine, declared in mounts.json and managed
from the Files sidebar’s + button (or the /api/mounts endpoints). The mount’s name
becomes a virtual top-level directory: the file explorer, editor tabs, and every agent
tool see it through the same multi-root filesystem and the same permission gate, and
nothing outside the workspace and its mounts is reachable.
Open the Files view in the activity bar and use one of the two buttons in its header:
+opens a native folder picker (macOS).✎takes a typed path —~is expanded — for headless daemons and everywhere else.

A mount appears as a top-level folder beside workspace/.
The mount is named after the folder’s basename, with anything outside [A-Za-z0-9_.-]
replaced by _; a name that’s already taken gets a -2 suffix rather than clobbering the
existing mount. The result is one line in <home>/mounts.json:
{ "myrepo": "/Users/you/repos/myrepo"}Removing a mount (the × on its root row, “Remove from Workspace” in its context menu, or
DELETE /api/mounts/<name>) drops that line.
Nothing in the folder is touched — a mount is a pointer, not a copy, and LatchAI never moves
or rewrites the directory it points at.
What the agent sees
Section titled “What the agent sees”Inside LatchAI, the mount name is the first path segment. myrepo/src/index.ts routes to the
mounted repo; anything that doesn’t start with a mount name stays in workspace/. That is
the same addressing the file explorer, editor tabs, read_file, write_file,
edit_file, list_dir, and run_shell’s cwd all use, so a path you can see is a path
an agent can name.
The gate is on the way out, not on the way in. A path that resolves outside the workspace
and its mounts is refused before anything is read or written, and the refusal is a
permission.denied event on the canvas and in the events panel, not a silent skip:
path escapes workspace root: ../../.ssh/id_rsaSymlinks are checked too — the real path of the target has to land inside one of the allowed roots, so a symlink planted in the workspace can’t be used to walk out of it.
Your first chat
Section titled “Your first chat”Open the assistant panel with ⌘⇧L. It runs on the same engine as everything else —
same provider, same tool registry, same filesystem — so it can read the repo you just
mounted, propose edits, and apply them. The file in your active tab is passed along as
context, and @ autocompletes a file path to attach its contents explicitly.
Try something that requires it to look around:
@myrepo/package.jsonwhat does the build script actually do, and is anything in scripts/ unused?
You’ll watch it work rather than wait on it: tool calls appear as chips in the chat and as
full entries in the events panel (⌘J), and if the model publishes a plan, the panel
grows a live to-do strip that ticks over as steps complete.
One picker at the bottom of the panel decides who is answering — a model (anything
in the Models page Library, on this Mac or connected)
or an agent (any persona in agents/, so you can talk to the same reviewer your
workflows use). ≣ lists saved sessions and reloads one; sessions survive engine
restarts because the envelopes live in runs/chats/.
Because every chat turn is checkpointed, the panel’s ± Review changes affordance shows you exactly what the assistant touched, with a revert.

Chat, the editor, and the file tree are one process — the assistant edits what you’re looking at.
Your first workflow
Section titled “Your first workflow”+ in the sidebar (or the “New Workflow” command) scaffolds a draft with a trigger node.
From there: add nodes from the toolbar palette, drag between handles to connect them,
click a node for its type-aware inspector, and press ⌘S to save. Saving validates the
graph server-side and writes workflows/<id>.json — a plain file you can read, diff, and
commit.
Step by step, for the smallest useful thing — an agent that reads the repo you just mounted and writes notes into the workspace:
+in the Workflows sidebar. You getUntitled 1, onemanual_triggernode, and a dirty tab (● in the tab bar). Nothing is on disk yet.- Add an agent node from the toolbar palette, and drag from the trigger’s output handle to the agent’s input handle to connect them.
- Click the agent node. The inspector is type-aware: give it a label, a prompt, and a tool allowlist. Click empty canvas instead and the inspector edits the workflow’s own name and description.
⌘S. The draft isPUTto the engine, validated against the workflow schema, and written to<home>/workflows/untitled-1.json. A validation error comes back into the toolbar rather than writing a broken file.⌘⏎. The run starts, nodes light up as they execute, tokens stream into the node, andfile.writtenshows up in the events panel when the agent writes.
The file you just made:
{ "id": "repo-notes", "name": "Repo notes", "description": "Summarize a mounted repo into the workspace.", "nodes": [ { "id": "trigger", "type": "manual_trigger", "position": { "x": 60, "y": 180 }, "config": {} }, { "id": "summarize", "type": "agent", "label": "Summarize the README", "position": { "x": 340, "y": 180 }, "config": { "tools": ["read_file", "list_dir", "write_file"], "prompt": "Read myrepo/README.md, then write a three-bullet summary of what this project is to notes/myrepo.md." } } ], "edges": [{ "from": "trigger", "to": "summarize" }]}Two things in that config are worth knowing. tools as an array is an allowlist;
true means every registered tool, and omitting it entirely makes the node a plain
single-shot prompt with no tool loop at all. And any {{nodeId}} in a prompt interpolates
that node’s output — {{trigger}} is the run payload, which is how a manual run,
a webhook body, and a cron fire all reach the same agent.
The { } JSON button in the toolbar toggles the canvas for the raw document. Both views
project the same draft, so an edit in one shows up in the other, and invalid JSON shows
its parse error instead of committing.
⌘⏎ runs the workflow, saving first if the draft is dirty, because the engine runs the
file on disk rather than the buffer in your browser. (If the trigger declares
config.params, LatchAI asks for them first and joins the answers into the payload.) The
same run, headless:
npm run run ~/LatchAI/workflows/repo-notes.jsonTroubleshooting
Section titled “Troubleshooting”The agent says it can’t find your repo. It is addressing a path outside its roots. Ask
for list_dir on . in chat — the top-level entries are the mount names plus the
workspace’s own contents, and whatever isn’t in that list isn’t reachable. If the mount is
in mounts.json but not in that list, the file was edited by hand while the engine was
running: restart it.
Where to go next
Section titled “Where to go next”- Workflows — the node types and the canonical format.
- Triggers — make the workflow run without you.
- Agents — turn a good prompt into a reusable persona.
- Checkpoints & review gates — put a human in the loop.