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.
Every connection starts at the broker. Inside your network, the plansmith-broker container opens an outbound HTTPS connection to app.plansmith.co and asks for work; Plansmith answers on that connection with one allowlisted read; the broker calls your Git server with your own credential, gets the file, and posts the response back. Nothing connects inward.
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.cofor work, and, from 0.4.0, the storage host named in each upload URL (a*.supabase.coaddress) 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.coand*.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.
- Open the group whose projects your team plans against. For a single repository, open that project instead.
- In the left sidebar, go to Settings → Service accounts.
- 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.
- In the group (or project), go to Manage → Members and select Invite members.
- Invite the service account. Search for the name you gave it; it appears with a generated username
like
@service_account_…. - Give it the role Reporter.
- 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.
- Back in Settings → Service accounts, open the ⋮ menu on the
plansmithrow and select Manage access tokens. - Select Add new token.
- Name it
plansmith-broker, pick an expiry your policy allows, and tick both scopes: read_api and read_repository. - 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.
- In Plansmith, open Settings → Connectors. You need to be an owner, on the Team or Enterprise plan.
- Under Self-hosted, select Add instance and pick GitLab.
- Where it asks whether the server is reachable from the internet, answer behind a firewall or VPN.
- 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 inTARGET_GITLAB_BASE_URLin step 5. Private addresses and internal hostnames are fine. - Select Add managed broker and name it after where it will run, for example
dc-frankfurt. Thepsbk_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.1PLANSMITH_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_URLpoints at the API or MCP host. Useapp.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.cois 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
httpsURL pointed at a plainhttpserver, or the reverse), or the server's certificate isn't trusted (setNODE_EXTRA_CA_CERTS). Runcurl -v https://<your GitLab server>/api/v4/versionfrom 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_deniedin 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_CERTSto 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.coover HTTPS, and look forsnapshotlines indocker 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.