Vibe Coding
Back to Multica

Multica / Workspaces and Teams

Project resources

Attach typed pointers (GitHub repository, local directory, more in the future) to the project so that the agent can automatically obtain the corresponding context when executing tasks.

Project resources key concepts infographic
Project resources key concepts infographic

Overview

Project Resource is a typed pointer attached to the project - today it can be attached to the GitHub repository URL or a directory on the local machine. In the future, there will also be Notion pages, document links, etc. When the agent runs a task on this project's issue, the daemon process will automatically write the project's resource list into the agent's working directory and insert it into its meta-skill prompt.

The result: the agent knows which repository to checkout (or which local directory to work in), and what the project's "main references" are - no one needs to copy and paste context into the issue description.

mental model

A project is no longer just a label, but a small resource container:

  • A project can attach 0..N resources.
  • Each resource has a resource_type (e.g. github_repo, local_directory), and a resource_ref (JSON payload stereotyped by resource_type).
  • To add a new resource type, you only need to add a string + a handler. **No schema migration is required, and no front-end rewriting is required. **

This shape is intentional - it is exactly the same as the design of the agent provider in Multica: a type discriminant field + a typed payload. The schema remains stable, and the addition of "Notion Page", "Google Doc", "Upload File", and "External URL" in the future will be small, incremental changes.

There are currently two built-in resource types: github_repo (an independent worktree is cloned for each task) and local_directory (which runs directly in the directory of the machine where a certain daemon process is located).

Resource type: githubrepo

Default resource type - each task is checked out in an isolated worktree:

{
  "resource_type": "github_repo",
  "resource_ref": {
    "url": "https://github.com/owner/repo",
    "default_branch_hint": "main"
  }
}

default_branch_hint is optional - once filled in, the daemon will write it into the meta-skill to tell the agent which branch to work on.

Resource type: localdirectory

For repositories that are not suitable for re-cloning for each task - game projects with dozens of GB, very large monorepo, or directories that themselves do not want to be copied multiple times - the project can be instead pointed to an existing directory on a daemon machine. The agent will run directly in this directory without cloning, copying or creating a worktree.

{
  "resource_type": "local_directory",
  "resource_ref": {
    "local_path": "/Users/me/code/big-game",
    "daemon_id": "0001234e-…",
    "label": "Main development directory"
  }
}

The trade-off compared to github_repo is intentional: only the bound daemon will use this local directory to execute tasks, and tasks in the same directory are executed serially, no longer in parallel. In exchange: your existing checkout, current branch, and uncommitted dirty changes remain the same - Multica will not re-clone.

When to choose local_directory and when to continue using github_repo

Focus github_repo (worktree mode) local_directory
Checkout cost of each task Re-clone + worktree 0 - the agent works in place
Concurrency in the same repository Any tasks in parallel One task at a time in the same directory
Branch/dirty changes Each task pulls a clean branch from the default branch Use the current state of the directory
Which machine can be run on Any daemon process Only the bound one
Disk usage One worktree per task 0 - use your existing directory

It is recommended to choose local_directory in the following two scenarios:

  1. The cost of re-cloning is too high - The time-consuming of git clone in a game checkout with dozens of GB, a monorepo with a large number of LFS resources, or any scenario will exceed the actual workload. You trade concurrency capabilities for "zero clone" operations.
  2. The changes are very small and require frequent local review - when you are polishing a certain component repeatedly, you have to switch back to the editor in a few minutes to take a look at what the agent has changed; instead of going to ~/multica_workspaces/ to flip a new worktree every time, it is better to let it work in your existing checkout.

The trade-off you accept is the same in both cases: The current version does not have any file-level write locks. The "serial execution of tasks in the same directory" gate is the only protection that prevents two issue agents from modifying the same file at the same time. If you point both issue agents to the same local_directory, their tasks will be queued rather than parallel - this is intentional. If you need true parallelism on the same code, continue to use github_repo.

Add local directory

Folder selector only available on desktop - the browser cannot read the OS path, so the "Add local directory" button is not displayed on the web. The process on the desktop is:

  1. Open the Project → Resources panel.
  2. Click Add local directory and the system will pop up the native folder selector.
  3. Select a folder. This path will be bound to the daemon currently registered on the desktop - both the path and the ID of the daemon are saved in the resource record.On the desktop, when the local daemon is offline, or the project has been bound to a local_directory on the local machine, the button will remain displayed but grayed out, and a line of explanation will be given below - so that you can see why it is temporarily unavailable. (The web page directly hides the entire button, because the web page does not have a folder selector.) To bind a directory on another machine, you need to install the desktop client on that machine and add resources there.

You can also use the CLI (even if the environment is purely web-based, as long as you provide the daemon ID yourself):

multica project resource add <project-id> \
  --type local_directory \
  --local-path /Users/me/code/big-game \
  --daemon-id <daemon-uuid> \
  --ref-label "main development directory" # optional

multica project resource update <project-id> <resource-id> \
  --local-path /Users/me/code/big-game-new

--daemon-id can be obtained through multica daemon list. The CLI also accepts the common form --ref '<json>' to pass the payload directly.

Path rules

When hanging resources and every time a task is started, the daemon will verify the path (the server is only responsible for storing JSON and does not verify it). If any of these conditions are not met, the task will fail with a typed error and your directory will not be touched:

  • Must be an absolute path.
  • Must exist and be a directory (cannot be an ordinary file, device node, or symlink pointing to a file).
  • The daemon process must be able to read + write.
  • Cannot be the system root or the entire user configuration area - /, /Users, /home, /root, /etc, /tmp, /var, /usr, /opt, /Users/Shared, your own $HOME, any Windows disk root (C:\, D:\...), and C:\Users / C:\ProgramData / C:\Program Files / C:\Program Files (x86) / C:\Windows.
  • Symlinks pointing to any of the above paths will also be rejected; macOS canonical aliases (such as manually entering /private/tmp) and /tmp are subject to the same restrictions.

The blacklist is intentionally aggressive - choosing your own home directory means Multica's runtime files will appear at the root of your account, which is not an outcome anyone wants. Please select a subdirectory (usually your specific project checkout).

Each daemon process has a maximum of one item per project.

On the same project, each daemon can only be bound to one local_directory** at most. If you want to add another one on the same machine, it will be rejected by the API with 409; the button on the desktop will be automatically hidden when the upper limit is reached, and the reason will be explained in the tooltip.Different daemons do not affect each other - a shared project can bind a local_directory to each teammate's machine, and the same project can be hung in different directories on different hosts. When the daemon process receives a task, it will select the line that matches its own ID and ignore the others.

Mixing resource types, and multiple local_directory resources

In fact, you will encounter two cross-resource combinations:

  • **The same project hangs both github_repo and local_directory. ** On the daemon with the matching local_directory binding, the local directory takes precedence: the agent runs in your directory, and this task will not create or use a worktree for the project's github_repo. (The repository cache of each workspace may still be synced as usual. This layer is a background behavior unrelated to specific tasks.) The URL of github_repo will still appear in .multica/project/resources.json and the ## Repositories section of the agent for reference, but the work tree that the agent is actually editing is your local directory, not the worktree. On other daemons that do not match the local_directory line (different machines, or the teammate has not mounted the local directory yet), the task follows the original github_repo worktree process. Essentially local_directory is an overlay of the worktree path by a daemon process.
  • **The same project hangs two local_directory. ** Each local_directory can only be bound to one daemon, so this will only happen between two different machines (if you want to add a second one on the same machine, it will be rejected by the API on the spot, see the previous section). The destination of the task is determined by the agent runtime binding, not by "which daemon has the local directory" - the task will fall on the daemon hosting the agent runtime, and it will pick out the line matching its own ID and ignore other lines. There is no load balancing here: if you want a certain machine to run a certain task, assign the task to an agent tied to that machine's runtime.

If a daemon does not match the local_directory line of this project, it will not be blocked because someone has tied it elsewhere - its tasks will go to other resources in the project as usual (usually the github_repo fallback path). local_directory only takes effect on the daemon it is bound to.

How to run the task in the local directory

When a task is distributed to an issue and the project has local_directory bound to the daemon process that receives the task, the daemon process will:

  1. Verify the path again (according to the above set of rules).
  2. Use the real path parsed by symlink as the key to obtain a "directory lock" - even if the two paths take different routes (one through symlink, one directly) to the same folder, they will be serialized.
  3. Write the agent’s CLAUDE.md / AGENTS.md (and .multica/project/resources.json) into your directory**. The agent works right there, and it's the same experience as opening it yourself and starting it.4. Multica’s own runtime products (output/, logs/, .gc_meta.json) are placed in an independent envRoot outside your directory.

If the first task is still running and the second task for the same directory comes again, the second task will stop in the Waiting for local directory (waiting for the local directory to be released) state. This status is visible in all relevant UIs - task status bars in chat, agent banners, execution records, activity indicators; waiting tasks will be counted in the agent's "queuing" occupation. Canceling the waiting task will immediately release its slot; canceling the running one will immediately push the next one.

There is no timeout for waiting - a waiting task will wait until the lock is released or canceled by the user/agent.

Multica can write but cannot move anything

  • Will write CLAUDE.md / AGENTS.md (or the equivalent file corresponding to the provider) and .multica/project/resources.json to the directory root - so that the agent has its own meta-skill and resource list. If you don't want to commit these to git, please add .gitignore.
  • will write all code changes the agent decides to make - no different than running the agent locally yourself.
  • Never physically delete your directory or anything inside it. Garbage collection has a judgment on the path: for the envRoot of local_directory, it will only clear the output/ and logs/ that are hung under the workspacesRoot, and will not touch your directory at all.

Limitations of the v1 stage (will gradually converge later)

The first version intentionally left more sharp edges than github_repo. The list below will be shortened over time; here is what it actually looked like today:

  • **Will not automatically cut branches. ** The agent runs on the branch you are currently checking out. If branch selection is important, switch it yourself before distributing tasks.
  • **Dirty workspaces will not be protected and will not be automatically committed. ** Uncommitted changes can be seen by the agent and may be modified in place, but will not be stash. Please treat this directory as a real workspace, and commit yourself before running the important things.
  • **PR will not be opened automatically. ** After the task is completed, the changes will stop on the branch where it was actually made - Multica will not push or open a PR. If you need push or PR, please do it yourself.
  • **waiting_local_directory only displays the status, not who is occupying it. ** This logo only tells you "Task is waiting", it will not tell you which task or which path is holding the directory.

These are listed in the "Agent Task Lifecycle" continuation of the local-directory job; until then, please use local_directory as if "the agent runs in your directory, just like you run it yourself".

Hang the repository when creating the project

On the web or desktop, when opening a new project, there is an additional Repos label next to Status / Priority / Lead. Select the repository that has been tied to the workspace (or paste a temporary URL), and they will be listed as githubrepo resources the moment the project is created.

Via CLI:

# Create + attach resources in one step. The resource is mounted in the same transaction -
# If any resource is illegal, the entire create will be rolled back and will never happen.
# "The project is built but only half of the resources are available" status.
multica project create \
  --title "Agent UX 2026" \
  --repo https://github.com/multica-ai/multica

# Subsequent management resources
multica project resource list <project-id>
multica project resource add <project-id> --type github_repo --url <url>
multica project resource remove <project-id> <resource-id>

# Any resource_type recognized by the server can use this universal entry ——
# No need to change the CLI when adding new types:
multica project resource add <project-id> \
  --type notion_page \
  --ref '{"page_id":"…","title":"…"}'

--repo can be specified repeatedly; each value will be hung as a separate github_repo resource.

What the agent sees at runtime

When the daemon starts an agent for an issue within a project, two things happen:

1. .multica/project/resources.json

Structured transparent transmission of the API response, written into the agent's working directory:

{
  "project_id": "…",
  "project_title": "Agent UX 2026",
  "resources": [
    {
      "id": "…",
      "resource_type": "github_repo",
      "resource_ref": {
        "url": "https://github.com/multica-ai/multica",
        "default_branch_hint": "main"
      }
    }
  ]
}

When a skill, auxiliary script, or the agent itself wants to get the most accurate resource list for this run, just parse this file.

2. The "Project Context" section in the meta-skill prompt

There is an additional human-readable summary in the agent's CLAUDE.md / AGENTS.md (different by provider):

Project Context

This issue belongs to Agent UX 2026.

Project resources (also written to .multica/project/resources.json):

Resources are pointers — open them only when relevant to the task. For github_repo resources, use multica repo checkout <url> to fetch the code.


This text is intentionally very concise: the complete payload has been written to the disk, and the prompt just gives the agent a position - letting it know that the project exists and what is hanging.

### On failure

Resource pulling is **best-effort**. If the API call fails, the project section in the prompt will be omitted and the file will not be written, but the task will start as usual - the agent will never get stuck due to lack of project context.

Add a new resource type

The whole purpose of abstraction is to make adding new types cheap. Complete process:

  1. Server-side validator (server/internal/handler/project_resource.go) - Add a case to validateAndNormalizeResourceRef to parse and normalize the new payload.
  2. Daemon meta-skill formatter (server/internal/daemon/execenv/runtime_config.go) - Add a case to formatProjectResource to let the agent prompt render the new type into a readable list item.
  3. TypeScript type (packages/core/types/project.ts) - add value to ProjectResourceType and add the corresponding payload interface.
  4. UI Rendering (packages/views/projects/components/project-resources-section.tsx) - Add a case to render the new type in ResourceRow.

No schema migration, no new sqlc query, no new endpoints, no changes to the CLI required - The CLI's universal --ref '<json>' option accepts any payload recognized by the validator and can be used on day 0. (If you want to add CLI shortcut parameters by type later, you can add them, but it is not necessary.)

Same project_resource table, same set of CRUD calls, for all types.

Workspace repository vs. project repository

The list of repositories seen by the agent (Repositories section in CLAUDE.md / AGENTS.md) is determined by the task-leading logic of the daemon process according to the following priorities:

  • The project has at least one github_repo resource attached → Only the repositories where the project is attached are displayed. The repository bound to the workspace will be hidden to prevent the agent from guessing which one belongs to this issue.
  • The project does not have the github_repo resource (or the issue is not in the project at all) → Return to the repository list in the workspace, as before.

This can compact the agent's working set: the project has clearly stated the repository, and that is the authoritative answer. And .multica/project/resources.json always contains a complete list, and skills that need to view all resources can still be obtained.

The daemon is also supported on the checkout side: when tasks come in with project-level github_repo URLs, these URLs are merged into a per-workspace whitelist and synchronized into the local repository cache before the agent is started. So even if a project repository URL is not bound to the workspace, multica repo checkout can handle it normally and will not reject it as "not configured". The division of the whitelist is only an internal implementation: the URL bound to the workspace and the URL at the task level are recorded separately, and refreshing the workspace repository list will not revoke a certain project URL.

Things not currently being done

These are intentionally left blank - the goal of the first version was to verify this abstraction with the smallest moving parts.

  • Share across projects. Each resource can now only be linked to one project.
  • Delimit resource visibility by skill. All resources are visible to every skill in this run of the agent; filtering by type is a follow-up task.
  • Cache/Sync. github_repo is currently metadata only - checkout is still done on demand with multica repo checkout. Document text caching like Notion / Google Docs is in place along with the corresponding type.