Skip to content

Projects

A project is a git repo that Thulu uses as the base for tasks. Thulu either clones it from GitHub or builds it from a folder you upload from your computer. When a task linked to a project leaves the first column, Thulu creates a task-<id> branch from the default branch and the agent works there. The project also stores what an agent needs to know about the repo: a description it reads at init, which agent runs in each stage, how to bring the app up in the sandbox, and where it runs in production.

A task can have no project (a research task, a question), but an agent working on code stays blocked until you link one.

  • To create or edit a project you need the admin role in the workspace. On an uploaded project, the developer role is enough to upload a new version of the folder and merge a task’s branch. With a lower role you can see the page, but the init script is read-only.
  • If the repo is private, you need one of two things: the GitHub App installed on your organization, or a GitHub token. Both are explained in Git and GitHub.
  • For the stage template to be useful, create the agents first. The template lists the columns of the board that are neither a start nor an end column: on the factory board, “In progress”.
  1. Open “Projects” in the sidebar and click “New project” (or press N).

  2. Type the “Name”. It is unique within the workspace and it is what you see on board cards, so keep it short (conduit-api, not “New version of the Conduit API”).

  3. Under “Where the code comes from”, leave “Git repository” selected (it is the default). The other option, “Upload a folder”, is explained in Create a project from a folder.

  4. Under “Repository access”, choose how Thulu reaches the repo. The modal preselects the option that asks the fewest decisions:

    • “GitHub App”: appears only if the operator configured the App. You pick the organization and the repo from two lists, and Thulu fills in the URL, the name (if it was empty) and the default branch. If the App is not yet installed on your organization, “Connect GitHub” takes you to GitHub and, when you come back, the modal reopens with what you had already typed. If your user is not an admin of the organization, GitHub leaves the installation pending until an admin approves it.
    • “Saved credential”: you pick a token from the credentials page. With “New credential…” you add it right there: name, “Personal access token” and “Save credential”. Thulu tests it against GitHub when you save it; if GitHub rejects it, it is saved anyway and a notice tells you it will not be able to clone. A classic token needs the repo permission (and workflow if agents will trigger Actions).
    • “Public repo”: clones without credentials.
    New project modal with GitHub App selected, the organization acme and the repo acme/payments-api (private) in the two lists; the name payments-api, the URL https://github.com/acme/payments-api.git and the branch main already filled in
    When you pick acme/payments-api, Thulu fills in the name, the URL and the branch. "Install on another organization" takes you to GitHub to add another organization.
  5. Paste the “Repository URL” (HTTPS, for example https://github.com/org/repo.git). A GitHub SSH URL is converted to HTTPS automatically.

  6. Check the “Default branch”. If you leave it empty, the project uses the repo’s own default.

    New project modal with the name coupons-api, Git repository selected as the source, Public repo, the repo URL, the master branch, the description and More options collapsed
    The required fields and the description go at the top. Board and template are under "More options".
  7. Write the “Description”. It goes into the prompt of every run that works on the project, so use it for what the agent cannot work out from the code: what the repo does and what it must not touch.

  8. Open “More options” if you want to change what is left:

    • “Default board”: you can leave it on “— instance default —”. It is only used when a task is created without a board (through the API, or by an agent from a chat). From the board, a new task always lands on the board you have open.
    • “Stage template”: the default agent for each stage. The table lists the columns that are neither a start nor an end column. A new task linked to this project starts with those agents already assigned.
  9. Click “Create”. The modal closes and the clone continues in the background: the card says “Cloning” until it finishes, and the project page updates by itself. If the clone fails, the card says “Clone failed” with the cause (for example, that the repository was not found) and git’s technical detail collapsed. A failed clone is not retried: delete the project and create it again once the cause is fixed.

Project list with two cards, billing-api and web-store, each with the URL, the description and the master branch
Each card shows the URL, the description and the default branch; if the template has agents, it also shows how many stages have them ("2 default"). One click opens the project.

If the code is not on GitHub, or you do not want to give Thulu access to your organization, you can upload the project folder. Thulu stores the repository itself, and agents work on it the same way as on a GitHub clone: one task-<id> branch and one worktree per task, and the init script and the dependency cache work the same.

What changes is the exit. An uploaded project has no pull requests, no CI and no review on GitHub, and the agents’ GitHub tools do not apply. A task’s work goes back to the main branch with “Merge into…” from the task, and the code leaves with “Download .zip” from the project.

Use When
“Git repository” The team works on GitHub and wants to review every change in a pull request, with CI and deploys through Actions.
“Upload a folder” The code is on your computer or reached you as a .zip, there is no GitHub involved, or it is a prototype where merging and downloading is enough.
  1. In “Projects”, click “New project” and, under “Where the code comes from”, choose “Upload a folder”.

  2. Drag the project folder onto the box or click “Choose folder” (the browser may ask you to confirm the upload). A .zip also works with “Choose .zip”; if the whole zip is inside one folder, Thulu strips it.

  3. Check what the browser left out before anything is sent. The box shows how many files remain and how big they are, plus two expandable lists:

    • “files left out as possible secrets”: .env and .env.<something> (except .env.example, .sample, .template), private keys (id_rsa, *.pem, *.key), .aws/credentials, .netrc and similar.
    • “folders left out”: node_modules, .venv, __pycache__, .next, .cache and other dependency or cache folders, wherever they are.

    The daemon applies the same rules again when the upload arrives. Symbolic links are not uploaded either.

    New project modal with Upload a folder selected, the folder recipes loaded with 10 files and 630 B, the list of 1 file left out as a possible secret with .env and the list of 2 folders left out with .cache and node_modules
    The name is filled in from the folder name. What is left out shows before you click "Create".
  4. If you want to change the “Name”, do it now: Thulu filled it in from the folder name. Write the description and open “More options” for the board and the template, as in Connect a repo.

  5. Click “Create”. The button goes through “Packing…” and “Uploading…”, with a progress bar, and the modal shows the result.

    New project modal with the result: recipes is ready, New repository. The folder is the first commit on main (05a364e), 10 files, 630 B, and the buttons Close and Open project
    Without a .git, the folder is the first commit on main.
  6. Click “Open project”. In the list, the card says “Uploaded to Thulu” where a cloned project shows the URL.

If the folder comes with its own .git with commits, Thulu keeps the history, the branches and the tags, and the branch its HEAD points to becomes the default branch (“History kept. main is at …”). Whatever differs from that commit goes in on top as a new commit. Without a .git, the folder is the first commit on main.

Limits: 200 MiB compressed, 20,000 files and 1 GiB uncompressed. The upload counts toward the workspace’s storage quota. thulu export carries an uploaded project’s repository inside the bundle, because there is no remote to fetch it from again.

On an uploaded project, the “Repository” tab shows the “Code” panel instead of “GitHub access”, and the “Repo” button at the top does not appear.

Code panel with the main branch and the Download .zip button, and below it New version with the folder recipes-v2 loaded: 11 files, 729 B, 1 folder left out, the notice that what you upload replaces what is on main and the Upload new version button
At the top you download a branch; below, you upload the whole folder as a new commit on main.

To download the code, pick the “Branch” and click “Download .zip”. The list has the main branch, the ones that came in the folder’s .git and, under “Task branches”, each task’s branch with its title once the task has merged or published its branch.

To change the code from your computer, upload the whole folder again:

  1. Download the main branch with “Download .zip” and work on that copy.
  2. Under “New version”, drag the folder or click “Choose folder” (or “Choose .zip”). The same exclusions apply as when you created the project.
  3. Click “Upload new version”. The panel confirms “New commit 0237388 on main.” or, if the folder is identical to the last commit, “Nothing changed on main”.

On a project cloned from GitHub, a task’s work reaches main through a pull request. On an uploaded project, you carry it with “Merge into…”, which appears in the “Branch and PR” row of the task panel once the task has a branch. If all of the task’s projects are uploaded, “Force PR” does not appear, neither in the row nor in the “…” menu.

  1. Open the task and, under “Branch and PR”, click “Merge into…”.
  2. Under “Into branch” choose the target; the main branch is the default. If the task has several uploaded projects, you pick the “Project” first.
  3. Click “Merge”.
Merge into modal over the panel of the task Search recipes by more than one ingredient, with Into branch main and the note Lands task-98765da233ea4753 on main: a fast-forward when main hasn't moved, otherwise a merge commit. Nothing is written if a file conflicts.
The modal's note says what will happen to the branch you chose.

Before merging, Thulu commits whatever the agent left uncommitted in the worktree and publishes the branch in the project’s repository, putting it on top of the current main. Then it fast-forwards main to the task’s branch if main has not moved, or creates a merge commit if it has. The notice “Merged into main (f20ef2c)” appears and the task’s activity adds “Merged task-… into main”. The task stays in its stage: move it yourself when it is time. If main already had everything, the notice says “main already has all the task’s commits”.

Panel of the task Document search in the README with the activity expanded: Created, Moved from Backlog to In progress, Branch created and Merged task-1d3734f28462fda0 into main (f20ef2c)
The merge event stores the commit.

If a file collides with the target branch, Thulu writes nothing and the modal lists the files:

Merge into modal for the task List newest recipes first with the red box 1 file conflicts with main; nothing was merged: src/routes/recipes.js
Another task had already changed the same lines of src/routes/recipes.js.

The task’s branch started from a main that no longer exists, and the change has to be redone on top of the current one. The modal offers two ways out:

  • “Redo on top of main” creates a copy of the task, with “(redone on main)” in the title and a note in the description that lists the files that collided. The copy starts a new branch from the current main when it leaves the first column. Merge the copy and delete the original.
  • “Return to the agent” appears only with agents on Kubernetes, where the agent can bring main into its branch and resolve in its own copy. It leaves a comment with the list of files: if a run is working on the task it receives it right away, and if not, the next run reads it. With agents on Docker the agent sees the .git read-only and cannot resolve it, so the button does not appear.

When you open a project you see four tabs. At the top right, “Tasks” opens the project’s default board with the search filtered by its name, and “Repo” opens the repo on GitHub.

Tab What it has
“Overview” Metrics for the work on the project over a date range: open and completed tasks, lead time, rework, runs, cost and where time piles up per stage.
“Environments” Where the repo runs (dev, staging, prod) and the connection to the cloud, telemetry and Evidence.
“Repository” Which credential or GitHub App installation it uses to reach GitHub (on an uploaded project, the “Code” panel), the init script with its dependency cache, the prompts and hooks per stage and the active worktrees.
“Settings” Name, URL, branch, board, description and template, and “Delete project”.
Overview tab of web-store with the 30-day range, 19 linked tasks, cards for Open, Throughput, Lead time, First-pass success, rework, runs, Model cache, cost and tokens, and the Flow and Spend charts
The Overview is computed from the tasks linked to the project. The 24h, 7d, 30d and 90d presets change the range.

The Overview metrics and how to read them are in Metrics and dashboards. “Open” counts every unfinished task, including those waiting in the first column, and “Model cache” refers to the model’s prompt cache, not the dependency cache.

The init script says how to bring the project up inside a task’s sandbox: what to install, which services to start and which port the app listens on. Agents run it with the sandbox MCP’s init_run tool instead of guessing the commands, and if it breaks (a new dependency, a port that changed) they fix it and save it as a new revision, which stays in the history and in the audit log with the agent as author. Without a script, every task starts from scratch. The sandbox itself is explained in Sandbox.

  1. Open the project and the “Repository” tab. The “Init script” panel shows the current revision, who saved it and its note.

    Repository tab of web-store with the GitHub access panel without a credential and the Init script panel on revision 2, saved by the developer agent, with its note and the script
    An agent saved revision 2: the "Agent" label and the note say what changed.
  2. Write the “Script”. It is bash, runs from the workdir and receives the params as environment variables. A new project starts with #!/usr/bin/env bash and set -euo pipefail. If Thulu recognizes the stack from a file at the repo root (npm, pnpm, Yarn, Go or pip), the panel says “This repo looks like …” and offers “Start from a template”, as long as the project has no saved revision. The script has to leave the app running in the background and exit.

  3. If the script needs values that change per task, add them with “Add param”: an uppercase name (PORT), a default, a description and “Required”. Names that start with OCTOPUS_ are reserved.

  4. Fill in “Ports” (where the app listens, comma-separated) and the “Ready command”: a command that exits with 0 once the app responds, for example curl -fsS http://localhost:3000/. init_run repeats it until it passes or until the “Ready timeout (s)” (300 by default, up to 900). “Workdir” is the repo folder where the script runs; . is the root.

  5. Write a “Note” (optional) with what changed and why: it is what shows in the history.

  6. Click “Save”. “Saved as revision N” appears.

Installing dependencies is what takes longest when you bring a project up. The collapsible “Install phase” section separates installation from startup so that Thulu can skip it or restore it from a snapshot.

Install phase section with the setup npm ci, tools node=20, the lock file package-lock.json with the red warning that it is not at the top of the default branch, the cache directory node_modules and No support files
A lock file that does not exist on the default branch is flagged in red: without it, setup never runs again.
Field What to enter Example
“Setup” The commands that install dependencies. It runs before the script and is skipped if its result is already there. npm ci
“Tools” Language versions, as name=version separated by commas. They are installed with mise once per workspace. Empty uses the repo’s mise.toml or .tool-versions. go=1.25.1, node=22
“Lock files” Files whose content defines the dependencies. If they change, setup runs again. package-lock.json
“Cache directories” What setup leaves in place and what is kept in the snapshot. Paths relative to the workdir. node_modules
“Support files” Files the script uses that the repo does not carry (a seed, a static server). They are kept in Thulu and the script reads them from $OCTOPUS_INIT_FILES. They are never written to the repo. seed-users.mjs

When init_run runs on a task, it does this in order: it installs the tools (from cache if they are already there), and then it skips setup if that checkout already installed with the same lock files, or restores the snapshot if there is one for that version, or runs setup with the workspace’s package caches already full. Only then does it run the script.

The prewarm builds the snapshot, from the default branch and in a separate sandbox: when you save a revision that changes the lock files or the setup, every 10 minutes if they changed on the default branch, and by hand with “Prewarm now”. The “Last snapshot” line shows the key, the date and the size. A task whose branch changed the lock files does not use the snapshot: it runs setup, which still downloads less because the packages are already in the workspace cache.

The “Dependency cache” panel in Infrastructure shows the operator the hit rate, how much time was saved on that installation and how much space each workspace’s cache takes.

Every save, yours or an agent’s, is a revision that is never deleted. An agent’s revisions carry the “Agent” label and say which agent saved it, in which stage and on which task, with a link to the task (“developer in Development · task #578dfc”). The change also goes in the workspace audit log.

  1. In the “Init script” panel click “History”.
  2. Pick a revision from the list. On the right you see its ports, ready command, params and the script.
  3. Click “Restore this revision” and confirm. It is saved again as the newest revision; the one that was current stays in the history.
  4. “Back to the editor” takes you back to the form.
Init script history with revision 2 by an agent and revision 1 by Sofia selected, showing its port 3000, the script with npm ci and the Restore this revision button
Restoring never overwrites: it copies the chosen revision as a new one.

In the “New task” modal, “Repos” shows the workspace’s projects as chips: click the ones the task will touch. When you create it, the task takes the agents from the template of each chosen project. If you choose several and two templates put different agents in the same stage, the one from the project that appears first in the list wins.

New task modal with the chips billing-api and web-store under Repos, billing-api chosen, the notice that its template has no agents and a title typed in
The chosen chip gets a border. Repos are optional.

A task that touches several repos has one task-<id> branch and one pull request per project. If you create it through the API without a board and its projects have different “Default board” values, Thulu rejects it until you name one.

The “Environments” tab describes where the repo really runs. With a named environment, an agent can ask for the error rate of prod instead of the project’s in general, and Evidence knows which URL to run its cases against.

  1. Under “Environments” click “Add environment”.
  2. Fill in the name (lowercase: dev, staging, prod) and, if you want, a “Display name” for Evidence and a “System” (the same value in all the repos of one deployment).
  3. Choose the provider. Today Thulu reads metrics from AWS and Google Cloud; Prometheus and “Other” can be registered but are not collected.
  4. Fill in “Region”, “Account” (AWS account id or GCP project id), “URL” and, if needed, “Selectors” (one key=value per line to narrow the resources).
  5. Check “Production” if it is the production environment. In Evidence, editing a production environment requires the admin role.
  6. Pick the cloud credential or create one right there, and click “Save”.
Environments tab of web-store with Production and Staging, each with provider other, its URL, the + attach credential link, an Evidence row with its URL and logins, and the Dashboards section
Each environment shows its connection and, below it, how Evidence uses it. The icons on the right are collect, telemetry, edit and delete.

What each environment enables is on other pages: metrics, telemetry and dashboards in Metrics and dashboards, and tests against the environment (test logins, schedule, Sandbox environment) in Evidence.

The “Settings” tab has the same fields as the creation form (except GitHub access, which is under “Repository”) with its own “Save”. Changing the template does not touch tasks that already exist.

Settings tab with name, URL, branch, board, description, stage template and the Danger zone
The template and deletion live on this tab.

Prompts and hooks per stage are on the “Repository” tab, in the “Stage prompts & hooks” panel. “Manage” opens a modal with one row per stage:

  • A prompt per stage is text added to the agent’s prompt when it works in that stage on this project (“in Review, run npm test before approving”).
  • A hook is a script that runs inside the worktree before the agent starts. Its output and exit code go into the prompt. Use it to hand the agent fresh data (the test results, the state of the migrations) without it having to ask.

Click “+” on the stage, write the file and save it with the button or with ⌘S. If you delete a stage from the board, its files stay as “Orphaned files” with a “Clean up” button.

Stage files modal of web-store with the sections Stage prompts and Pre-launch hooks, one undefined row per stage and a + button on each
Prompts and hooks are defined per project and per stage.

Use the trash can on the card in “Projects” or “Delete project” in the “Danger zone” of “Settings”. Thulu asks for confirmation, deletes the clone and all the worktrees, and the linked tasks are left without a project. It cannot be undone, and the branches of tasks that were not pushed are lost with the clone: push anything you want to keep first. An uploaded project has no remote: Thulu also deletes the repository it was storing, so download the branches you want to keep first.

  • The agent does not start on a task with a project. Check whether the task has an agent in that stage. The template is copied only when the task is created with the project already linked.
  • The agent reinstalls everything on each task. The script has no “Install phase” or is missing “Cache directories”: without those there is no snapshot. If the snapshot block says “Fill in setup and cache directories, and save”, that is the cause.
  • “Merge into…” does not appear. The task does not have a branch yet (it is still in the first column) or none of its projects is uploaded. It does not appear on a subtask either: the branch belongs to the parent task.
  • “Create” with “Upload a folder” uploads nothing. If the box says “No files left to upload…”, the folder had only dependencies or secrets. If the notice says “The upload weighs … the limit is 200 MiB”, leave out the builds or the heavy files and try again.
  • “Prewarm now” does not appear. You need the admin role and an installation with the sandbox runtime.
  • The project card says “Clone failed”. The cause is in the notice on the project page; fix it (URL, branch, credential), delete the project and create it again.
  • The agent changed the script and now it does not come up. Open “History”, compare with the previous revision and restore it.