Plansmith self-hosted Git broker

Let Plansmith read a Git server inside your network, without opening a firewall port and without giving us your credentials.

When you need one

If your GitLab, GitHub Enterprise or Bitbucket server is reachable from the internet, you do not need this. Register it directly and skip this page. The managed broker exists for the other case: a server that only answers inside your network, behind a VPN or a corporate firewall.

Self-hosted Git servers, including the broker, are available on the Team and Enterprise plans.

How it works

You run a small container inside your network. It dials out to Plansmith over TLS and asks for work. When a planning session needs to read your repository, we put a request on a queue; your broker picks it up, adds your own Git credential, calls your server, and posts the response back.

From version 0.4.0 the broker also indexes each linked repository. Once per new commit on the linked branch, it downloads that commit's archive from your GitLab, keeps the text files, packs them into one compressed file with an index, and uploads it to Plansmith's storage over HTTPS. Plansmith searches that index, so a search of the whole repository returns in milliseconds instead of reading files one at a time through the queue. Until a repository is indexed, the planning agent still searches it by reading its files one at a time, and says which files it could not read yet.

From version 0.4.1 the index includes files saved in older encodings, such as Latin-1 (Windows-1252), which is common in database packages and older source. They are converted to UTF-8 in the index, so the agent searches them like any other file. A broker on 0.4.0 keeps serving the index it already built, and Settings → Connectors says it needs an update. As soon as it reports 0.4.1, Plansmith indexes its repositories again.

One broker reaches one server. The container is configured with a single target URL, so if you have two internal Git servers you run two brokers, each with its own token. Plansmith enforces this: a broker that already reaches a server cannot be chosen for a second one.

What your security team will ask

  • Inbound ports: none. The broker makes outbound connections only. There is no firewall rule to add, no inbound route, and no VPN access for us.
  • Outbound destinations: two. app.plansmith.co for work, and, from 0.4.0, the storage host named in each upload URL (a *.supabase.co address) for the index. Each upload URL is signed for one file, expires within hours, and cannot be used to read anything.
  • Where credentials live: only in your network. Your Git token is an environment variable on your container. Plansmith never receives it, never stores it, and cannot read it. The broker overwrites the authorization header on every request with your token, so nothing we send can substitute a credential of ours.
  • What can be reached: whatever the service account or API token you create allows. You set the reach in your own Git server, and Plansmith cannot exceed it: the broker holds no credential of its own, so an account with read access to two projects is an integration with read access to two projects. The container narrows it further, to the handful of read-only API calls Plansmith actually makes; a request outside that set is refused before it reaches your server, and the refusal is logged. No write endpoint is reachable through the broker at all. From 0.4.0 that set includes the repository archive endpoint, which the broker calls itself to build the index. From 0.5.0 it also includes what an acceptance review needs: reading one merge request by number, comparing two commits of the same project, and searching merge request titles, descriptions and branch names for one issue tracker key. The search needs that key: the broker cannot list merge requests or branches any wider.
  • Audit: one JSON line per relayed request to the container log, recording timestamp, method, path, decision, status, duration and byte counts. Never request bodies, never file contents, never tokens.
  • What we store, and for how long. What your broker returns is the context the planning agent works from, so it lives in that plan's chat for as long as the chat does. Getting it there uses a queue in our database, and those rows are deleted as they are read; anything a failed request leaves behind is scrubbed within the hour, and revoking a broker deletes its queued rows on the spot. Archive a plan and it is deleted 30 days later, its chats and everything read into them included.
  • The index (0.4.0 and later). The text files of each linked repository at its two most recent commits, encrypted at rest and readable only by your team's planning agent. Binary files and files over 4 MB are left out; from 0.4.1, files saved as Latin-1 are kept, converted to UTF-8. Older commits are deleted as newer ones replace them, and every stored copy is deleted within minutes of the last project unlinking the repository. While a conversation searches it, the server answering holds a working copy that is removed as it ages out. The build logs one audit line per archive download.

Setting up your server

Select the git server you run.

The setup takes about fifteen minutes across three places, and each step names where you are. You will need:

  • Owner access to the GitLab group or project you want to plan against, for steps 1 to 3
  • A Plansmith owner on the Team or Enterprise plan for step 4
  • A host inside your network with Docker, able to reach the GitLab server and to make outbound HTTPS to app.plansmith.co and *.supabase.co, for step 5.

In GitLab: create a service account. A service account is GitLab's non-human account for exactly this: it has no password and no inbox, nobody can sign in as it, its reads appear in your audit logs under its own name, and it holds exactly the access Plansmith needs and nothing else.

  1. Open the group whose projects your team plans against. For a single repository, open that project instead.
  2. In the left sidebar, go to Settings → Service accounts.
  3. Select Add service account and name it plansmith.

Create it on the group when several projects should be available; a project-level service account covers that one project. Creating one takes an owner of the group or project.

If Settings → Service accounts is not in your sidebar, your GitLab is older or on the Free tier. The fallback works the same way: an administrator creates a dedicated regular user in the Admin area (Overview → Users → New user), signs in as it once, and mints the token under Edit profile → Access tokens. Steps 2 and 3 are unchanged.

In GitLab: give it read access to the right projects. A service account starts with access to nothing, and its token can only see what the account can see. Membership is where you decide which code Plansmith can read; nothing outside it is reachable, whatever the token says.

  1. In the group (or project), go to Manage → Members and select Invite members.
  2. Invite the service account. Search for the name you gave it; it appears with a generated username like @service_account_….
  3. Give it the role Reporter.
  4. Repeat for any other group you want available. Subgroups and their projects inherit the membership.

Reporter is the floor. On a private project the Guest role can see that the project exists but cannot read its files, so a Guest membership passes every screen here and then produces empty file lists in step 6.

In GitLab: create the access token. Tokens for a service account are managed from the same page you created it on. You never sign in as the account.

  1. Back in Settings → Service accounts, open the ⋮ menu on the plansmith row and select Manage access tokens.
  2. Select Add new token.
  3. Name it plansmith-broker, pick an expiry your policy allows, and tick both scopes: read_api and read_repository.
  4. Create it and copy the glpat- value straight away. GitLab shows it once.

Both scopes, deliberately. The broker talks to the GitLab REST API, and the scope that opens it is read_api. A token with only read_repository looks right, is accepted here, and then fails step 6 with 403: that scope covers Git operations, not the API. Both scopes are read-only.

In Plansmith: add the server and get a pairing token.

  1. In Plansmith, open Settings → Connectors. You need to be an owner, on the Team or Enterprise plan.
  2. Under Self-hosted, select Add instance and pick GitLab.
  3. Where it asks whether the server is reachable from the internet, answer behind a firewall or VPN.
  4. Enter the server URL, for example https://gitlab.example.internal. This is how the server appears in Plansmith; traffic goes wherever your container is configured to reach, so use the same URL here and in TARGET_GITLAB_BASE_URL in step 5. Private addresses and internal hostnames are fine.
  5. Select Add managed broker and name it after where it will run, for example dc-frankfurt. The psbk_ pairing token is shown once, with the command for step 5 pre-filled.

Keep this tab open: the broker flips to Online here the moment the container makes its first poll.

On your host: run the container. On the Docker host inside your network, run the pre-filled command from step 4. It looks like this:

docker run -d --name plansmith-broker --restart on-failure:5 \
  -e PLANSMITH_URL=https://app.plansmith.co \
  -e PLANSMITH_BROKER_TOKEN=psbk_… \
  -e TARGET_GITLAB_BASE_URL=https://gitlab.internal \
  -e TARGET_GITLAB_TOKEN=glpat-… \
  ghcr.io/braingridai/plansmith-broker:0.5.1

PLANSMITH_URL: always https://app.plansmith.co. Not the API or MCP host, which serve a different surface and will not answer a broker poll (the container refuses to start if you point it at one).

PLANSMITH_BROKER_TOKEN: the psbk_ token from step 4.

TARGET_GITLAB_BASE_URL: your GitLab as reachable from this container, the same URL you entered in step 4.

TARGET_GITLAB_TOKEN: the glpat- token from step 3. It never leaves your network.

HTTPS_PROXY / NO_PROXY: optional, honoured if your egress goes through a proxy. NODE_EXTRA_CA_CERTS: optional, point it at your CA bundle if a proxy terminates TLS or your server uses a private CA.

AUDIT_LOG_PATH: optional. The audit log always goes to stdout; this also writes it to a file.

Verify end to end. Within a few seconds of the container starting, the broker shows Online in Plansmith. Finish saving the instance: Plansmith verifies it by asking your broker to call GET /user on your GitLab, with your token, from inside your network. When that succeeds, pick the repositories your team should plan against. The list you are choosing from is the service account's membership from step 2.

For a final check, attach one of those repositories to a plan and ask the planning agent something about the code. The answer arrives through the broker, and docker logs plansmith-broker shows one JSON audit line per file read.

If something is wrong

Everything below is about a running broker, so it is the git server's path.

  • A startup error naming the host. PLANSMITH_URL points at the API or MCP host. Use app.plansmith.co.
  • Polls return 404. The pairing token was revoked, or mistyped. Add a new managed broker (step 4) and restart the container with its token.
  • It stays Offline. Outbound HTTPS to app.plansmith.co is blocked, or your proxy variables are not set on the container.
  • It reads "Can't reach GitLab". The container polls Plansmith but cannot open a connection to your GitLab server: three reads in a row failed. Settings → Connectors says since when and why: the server name doesn't resolve (DNS, or the server itself is down), the connection was refused or dropped, it timed out, the secure connection failed (an https URL pointed at a plain http server, or the reverse), or the server's certificate isn't trusted (set NODE_EXTRA_CA_CERTS). Run curl -v https://<your GitLab server>/api/v4/version from inside the container to see the same error. Anything already indexed is still searchable meanwhile. Plansmith checks each repository for new commits about every 15 minutes through the broker, so the status clears on its own soon after the server is reachable again. A broker older than 0.5.1 doesn't report why: pull the new image and recreate the container with the same settings (a restart keeps the old image).
  • Verification fails with 403 on a fresh token. Two different halves fail identically from outside: the token is missing the scope that opens your server's REST API, or the account it belongs to is not a member of the group or project. Check both; your product's tab above names the scope and the role.
  • Repositories are missing from the picker, or file lists come back empty. The account's membership does not cover them, or covers them at a level that can see a project exists but not read its files. Widen the membership; a broader token will not fix it, because the token can only ever see what the account can.
  • allowlist_denied in the log. Working as designed: the container relays a fixed set of read-only API calls, and something outside it was requested and refused before reaching your server. Send us the line if you think that call should be allowed.
  • TLS errors. Set NODE_EXTRA_CA_CERTS to your CA bundle.
  • The planning agent says the repository is still being indexed. Plansmith notices a new commit on the next conversation that reads the repository, or within about 15 minutes, and the broker then builds the index in a minute or two. Until then the agent searches by reading files one at a time. Settings → Connectors shows each repository's index status. If it never finishes, check that the container can reach *.supabase.co over HTTPS, and look for snapshot lines in docker logs plansmith-broker.
  • Your monitoring shows requests with no response status. Expected. An idle broker holds a poll open for up to 25 seconds waiting for work, and some tools log the open request with an empty status. It completes as an ordinary 200.

Upgrading and shutting down

Pin a version tag or an image digest and redeploy to upgrade. To stop a broker for good, revoke it under Settings → Connectors, in the self-hosted section: its token stops working immediately, whatever version the container is running, and anything still queued for it is deleted.

Settings → Connectors is open only while your workspace is active, so if it is suspended or has a billing problem you will not be able to revoke from there. Email hello@braingrid.ai and we will revoke your brokers for you. We treat a running relay you want stopped as urgent, whatever the state of your account.

To move a server to a different broker, start the new broker, then add the server again in Plansmith with the same URL and pick the new broker. Same URL means it updates the server you already have rather than creating a second one, and the repositories you have already linked keep working, so there is nothing to re-link.