Parallel multi-host git: one process, every host

Lorenzo Wynberg10 min readEngineering
Three projects running concurrently, each pinned to its own git host, transport, and identity — GitHub over SSH, GitLab over HTTPS, and self-hosted Forgejo

Most AI coding tools assume there is exactly one of you. One GitHub login. One git config. One remote. That assumption holds right up until a real team opens its real laptop — where work lives on the company's GitHub org, a side project lives on GitLab, an open-source fork lives on a self-hosted Forgejo, and you are a different person on each.

Trinity treats git as pluralistic. The host, the transport, the credential, and the identity are all per-destination configuration — not one global git context the whole machine shares. So you can run different projects against different git hosts, over different transports, as different accounts, all at once. Trinity's agents push to each using the right settings automatically.

This post is about the machinery that makes parallel multi-host git work — and why it doesn't collide.

The single-context assumption breaks teams

Open your terminal and run git config --global user.email. There is one answer. Run ssh-add -l. One agent, holding keys for the whole session. Set GH_TOKEN and it applies to every gh command you run. Git, as most tools wire it up, has a single ambient context: one identity, one credential, one door to the outside world.

That is fine for one human on one project. It falls apart the moment the work fans out.

You're shipping to the company org as your work account. The same afternoon you want an agent to open a PR on your personal GitLab project — as you, not the company bot. Next week a client's repo lives on their self-hosted Forgejo behind SSH-only access. With a single git context, each of these means stopping to re-point the global config, swap the active token, or juggle ssh-agent entries. Do two of them at the same time and they fight over the same global state.

For a tool that runs agents in parallel, a single shared git context isn't an inconvenience. It's a correctness bug waiting to happen.

Pluralistic git: a destination per scope, not a context per machine

Trinity inverts the assumption. There is no one git context. There is a destination — and a destination carries everything a push needs: which host, which door, which credential, which identity.

Running concurrently
Project A
HostGitHub org
DoorSSH key
As@work
Project B
HostGitLab group
DoorHTTPS token
As@personal
Project C
HostSelf-hosted Forgejo
DoorSSH key
As@bot

One process. Three destinations. No shared git state to collide over.

Three projects, three destinations, running concurrently in one process. Project A pushes to the team's GitHub org over SSH as your work account. Project B pushes to your personal GitLab over an HTTPS token. Project C pushes to a self-hosted Forgejo over SSH as a dedicated bot. None of them reaches for a shared global setting, because there isn't one to reach for.

A destination belongs to a scope — either personal or a specific team. That scoping is what lets your personal GitLab account and your team's GitHub org coexist without ever stepping on each other. Switching projects doesn't reconfigure git. It just resolves a different destination.

note

Tokens didn't go anywhere. HTTPS with a personal access token is still the default door and is fully supported. SSH is the new opt-in transport you choose per destination. The point isn't SSH over tokens — it's that you pick the door per destination, and Trinity remembers.

How a destination gets chosen

When an agent is about to push, Trinity has to answer one question: which destination? It resolves in tiers, narrowest first, and the first tier that answers wins.

First match wins

1
This repoA per-repo override pinned to one destination
2
This projectA project-wide destination for every repo it owns
3
The scope defaultThe default destination for personal or a specific team
4
Ambient accountThe single connected account, when nothing more specific is set

Override the narrow, inherit the broad — most repos never need more than the default.

A single repo can pin its own destination. If it doesn't, the project it belongs to can set one for all its repos. If the project stays quiet, the scope's default destination applies. And if you've only ever connected one account, that ambient account is the fallback — so the simple case stays simple and the complex case is expressible.

This is why a polyrepo project can hold one repo on GitHub and another on Gitea without any special mode. Each repo resolves its own destination at operation time. The override lives exactly where the exception lives, and everything else inherits the broad default. You can wire this up in project settings, where each repository can override the project default, and pick the destination and identity for a team's repositories right at team setup.

The door: HTTPS token or managed SSH, per destination

Every destination has a transport — Trinity calls it the door. It's a property of the destination, resolved at clone time, not a machine-wide choice.

The default door is HTTPS. Trinity hands git a token through GIT_ASKPASS so credentials never land in a remote URL or a log line. Nothing about that changed in this release; tokens remain first-class.

The new door is managed SSH. Choose it on a destination and Trinity generates a key pair, enrolls the public key with the host, and from then on runs git with GIT_SSH_COMMAND pointed at that specific private key — with IdentitiesOnly=yes, so SSH uses the key you mean and ignores whatever else is in your agent.

Credentials are local, per device, per host

Private keys live in a local credential store, partitioned per account and per host, with the key itself at 0600. They are never synced. Each machine you work from enrolls and holds its own key — so a leaked laptop is one key to revoke on one host, not your whole git life.

The transport choice is sticky. Re-saving a destination without touching the door leaves the door exactly as you set it — a small thing, but it's the difference between trusting the setting and re-checking it every time.

Identity: many accounts, even on the same host

Door answers how you connect. Identity answers as whom. And on a single host, that answer is rarely just one thing.

You are one person on github.com for your employer's org and a different person on github.com for your own repos. Trinity models identity in two tiers per host: a host-level default for the account, and per-namespace overrides for specific orgs or users. So a work account scoped to the team's org and a personal account everywhere else live side by side on the same host, and the right one is chosen by where the push is headed.

That's also why an agent can author a commit as a project's dedicated bot on one repo and as your own account on another, in the same run, without you switching anything.

Four hosts, one contract

A destination is only useful if Trinity can actually speak each host's API. Behind a single Forge interface, Trinity implements GitHub, GitLab, Gitea, and Forgejo (with Bitbucket present too), each with a compile-time capability floor so a missing capability is a build error, not a runtime surprise.

ConcernSingle-context gitTrinity
HostOne, implied by the remotePer destination — GitHub, GitLab, Gitea, Forgejo
TransportOne global defaultPer destination — HTTPS token or managed SSH
CredentialShared ssh-agent / global tokenPer account, per host, local to the device
IdentityOne global userHost default plus per-namespace overrides
Parallel pushesFight over global stateIndependent — nothing shared to clash

The forge layer also learned to count. List endpoints now paginate to completion and respect each provider's maximum page size — so a large account returns all its repositories instead of silently stopping at the first page, and never trips a provider's page-size limit. Small detail, but "we only saw your first 30 repos" is exactly the kind of bug that erodes trust in everything above it.

Why it doesn't collide in parallel

Here's the part that makes all of the above safe to run at once.

Every running story resolves its own host, transport, credential, and identity at operation time, and pins them for the duration of that work. All authentication is then threaded per command through environment variables — GIT_SSH_COMMAND, GIT_ASKPASS, GH_TOKEN — on the exact invocation that needs them.

What Trinity never does: git config --global. No shared ssh-agent. No process-wide token. There is no global git state for two concurrent operations to overwrite.

The usual way

One git config --global and a shared ssh-agent for the whole machine

Story 1 sets the global identity
Story 2 overwrites it mid-push

Two concurrent pushes fight over one global state.

Trinity's way

Auth threaded per command via environment variables — no global config, no shared agent

Story 1
GIT_SSH_COMMAND=ssh -i alice.keyGitHub over SSH as Alice
Story 2
GIT_ASKPASS + GH_TOKEN=bobGitLab over HTTPS as Bob

Nothing is read from a process-wide place, so two pushes never overwrite each other.

That is precisely why two stories can push at the same instant — one to GitHub over SSH as Alice, one to GitLab over HTTPS as Bob — inside the same process, with zero chance of one clobbering the other. The auth rides on the command, not in a place the next command reads. We audited the codebase specifically for shared git state that two operations could trip over. There wasn't any to find.

tip

This is the part that turns multi-host from a feature into a capability. Anyone can support more than one host if you do them one at a time. Doing them simultaneously, as different identities, in one process, is a property of how the auth is plumbed — and it's the reason Trinity's parallel story execution works across hosts at all.

Where the field is, honestly

This is differentiation in degree and integration, not a claim that nobody else thought of multiple hosts. Plenty of tools have. The pattern across the leading autonomous coding agents is that each is built around a single host and a single connected identity.

GitHub's Copilot coding agent is GitHub-only by design — its documentation is explicit that for repositories on other hosts it "won't be able to work on it." OpenAI's Codex cloud agent documents only GitHub, authenticating through GitHub App installation tokens. Cursor's cloud agents were GitHub-first with partial GitLab support as of early 2026. Devin spans multiple hosts, but steers you to a single dedicated bot account rather than your own identities. And CLI agents are host-agnostic only because they defer to your local git config — whatever single context that happens to hold — and their parallelism runs across worktrees of the same repo and remote.

So the honest framing: as of mid-2026, the agents that span multiple hosts typically do so one connection or one bot account at a time. Running many projects with different hosts, transports, and identities side by side — that's the gap Trinity is built to close.

One process, every host

The single-context assumption was never wrong. It was just sized for one person on one project. Real teams outgrew it the moment their work spread across hosts and accounts, and most tooling is still trying to paper over that with a global config swap.

Trinity sizes git to the team instead. Host, transport, credential, identity — each is per-destination, each resolves at operation time, and each rides on the command rather than a shared context. That's what makes the same process push to your company's GitHub, your personal GitLab, and a client's Forgejo at the same time, as the right person on each.

If you remember one thing: git doesn't have to be singular. Configure the destination once and Trinity handles the rest — every project, every host, in parallel. See the full release in the 0.3.2 changelog.