# What works with what

Three things have to line up before a single command runs: a **transport** to
carry it, a **station** to run it, and a **controller** to publish it from. This
page is all three, and which combinations actually work.

The tables are generated from one source, so they cannot disagree with each
other. Where a row says something is unproven, that is the honest state rather
than modesty.

## Every transport is one of two shapes

This is the distinction the whole design rests on, and it decides more about an
estate's answer than any other fact on this page.

| | |
|---|---|
| **pigeonhole** | A dead letter drop. You cannot reach the far side, the far side cannot reach you, and **both can reach one agreed place**. Both sides dial out; neither ever accepts a connection |
| **intercom** | You can reach the station's endpoint directly, so there is no drop in the middle. Unusual, because the whole tool exists for when you cannot |

**Everything shipped is a pigeonhole.** git, the relay, a file share, Azure
Blob, an object store and a bundle are all the same shape: something is left
somewhere, and collected later by the other side. The words come from the
station itself - `pigeonhole.sh` and `intercom.sh` have carried them since
before this page existed.

### Why the shape matters more than the speed

The obvious difference is latency, and it is the less important one.

**A pigeonhole keeps the gates where they belong.** The step is already on the
far side, the station reads a request that names it, and the station decides
whether to run it. `# heliograph-mode: read-only` is a property of a file the
operator can read before anything happens.

**On an intercom the script travels with the request.** There is no git on the
far side to have planted it, so the caller ships the thing to be run - and at
that moment the mode header stops being a control and becomes *a claim the
caller makes about its own file*. What is left is the function key and an IP
allowlist. That is a real trade and [the intercom page](/intercom) makes it in
full; it is not a worse transport, it is a different security model.

**A pigeonhole also needs nothing to be reachable, ever.** No inbound port, no
endpoint, no tunnel. That single property is what makes this permissible in
estates where a reverse connection would be a breach, and it is why [a raw TCP
transport was dropped](/security) rather than built.

## Seventy combinations, and most of them do not work

Ten stations by seven transports. Pick a cell for the whole story, filter by
the controller you would drive it from, or use the arrow keys. Every cell that
cannot run says why rather than leaving you to go and ask somebody.

| station | git | relay | file share | Azure Blob | object store | bundle | intercom | 
|---|---|---|---|---|---|---|---|
| Terminal | Proven | Proven | Proven | Proven | no | no | Works - only where the station's endpoint is reachable from your side | 
| Docker | Proven | Proven | Proven | Proven | no | no | no | 
| Kubernetes | Proven | Proven | Proven | Proven | no | no | no | 
| As a service | Proven | Proven | Proven | Proven | no | no | no | 
| Windows task | Proven | Needs a step - the PowerShell relay transport is not written yet - issue #77 | Proven | Works - through the bash station, not the PowerShell one | no | no | no | 
| A pipeline | Works - the push is the trigger, which is the entire point | no | no | no | no | no | no | 
| Azure containers | Proven | Needs a step - the two key files have to be put on the host - no template mounts a volume | Needs a step - the share has to be mounted | Proven | no | no | no | 
| Azure VM | Proven | Needs a step - the key files, and a git host reachable once at first boot | Needs a step - the share has to be mounted | Proven | no | no | no | 
| Function App | Needs a step - there is no git in the image | no | no | Works - through pigeonhole.sh, on a timer | no | no | Works - the one host with an endpoint you can reach | 
| Your own host | Never checked - meets the contract, never run | Never checked - meets the contract, never run | Never checked - meets the contract, never run | Never checked - meets the contract, never run | no | no | no | 

**Every transport**

| transport | shape | your side | the far side | what it is |
|---|---|---|---|---|
| git | pigeonhole | proven | proven | A private repository is the channel in both directions. The only transport that can bring the station a newer copy of itself. |
| relay | pigeonhole | proven | works | Both sides dial out over ordinary HTTPS. No git host, no storage account, no VNet - and the only transport needing a binary on the far side. |
| file share | pigeonhole | proven | proven | The cheapest there is, where both machines already mount the same directory. The mount is the credential, and that is the whole security model. |
| Azure Blob | pigeonhole | with a condition | works | Reached through drop.sh in the station payload rather than the heliograph binary. A VNet-local private endpoint is often the only thing reachable. |
| object store | pigeonhole | works | none yet | S3-compatible: AWS, R2, MinIO, B2, Spaces, Ceph. The CLI drives it and no station can read one, so nothing in this column can work yet. |
| bundle | pigeonhole | works | none yet | The only thing that makes air-gapped literally true. A person carries the file. No station can read one yet. |
| intercom | intercom | works | with a condition | The one case where you CAN reach the station. The script travels with the request, so heliograph-mode stops being a control and becomes a claim the caller makes about its own file. |

**Every station**

| station | status | flavour | what it is |
|---|---|---|---|
| An operator's terminal | proven | bash, PowerShell | Still the best host when there is a willing person: no infrastructure request, and start.sh prints its own preflight to somebody who can read it. |
| Docker | proven | bash | CI builds the image and runs a loop inside it. The image plants the payload itself, so a transport with nothing to clone still has one. |
| Kubernetes | proven | bash | The same image, one replica. CI applies the manifest to a real cluster on every run. |
| systemd, launchd, setsid | proven | bash | Survives a logout. A detached process inherits nothing from the shell that installed it, so the transport's variables go in .station-env. |
| Windows scheduled task | proven | PowerShell | CI registers the task and reads it back. .station-env is the only way to hand it a token: there is no EnvironmentFile on Windows. |
| GitHub Actions, Azure Pipelines | written, never run | bash | Often the one machine in an estate that can already reach both sides. Git-only by design: the push is the trigger, and that is what keeps the latency down to however long an agent takes to start. |
| Azure ACI, Web App, Container Apps Job | proven | bash | Deployed live, then torn down. The templates take a transport through two maps rather than a parameter per transport, so a new transport does not date five templates at once. |
| Azure VM | proven | bash | Deployed live on a Standard_D2s_v3. The exception to everything else here: a bare VM has no image, so git clone is how the toolkit arrives whatever transport then carries the logs. |
| Azure Function App | written, never run | bash | A timer, not a loop. Validated and never deployed. It is the host the intercom was written for, because a Function App has a public endpoint while sitting inside the VNet. |
| ECS Fargate, Cloud Run, anything else | none yet | bash | Recipes against the host contract, not templates. Issue #5 settled that deliberately: a template that has never started a station spends the credibility of the ones that have. |

**Every controller**

| controller | status | transports | what it is |
|---|---|---|---|
| The CLI, on Linux, macOS or Windows | proven | git, relay, file share, object store, bundle, intercom | One static binary, amd64 or arm64, no runtime. Only the git transport shells out to anything - the rest are the binary alone. |
| An AI agent, over MCP | proven | git, relay, file share, object store, bundle | heliograph mcp serves the same commands as typed tools. The gates do not move: a tool call publishes a request, and the station still decides whether to run it. |
| git and no binary at all | never checked | git | Already possible and documented nowhere. A request is a key: value text file, so a laptop that permits git and refuses new binaries can write it, push it, and read the log back out of the repository. |
| Claude Code on the web | never checked | git, relay | A cloud sandbox is a real shell, so the binary simply runs. The session proxy decides the rest: private and self-hosted hosts are refused by the allowlist. |
| Android, through Termux | never checked | git, relay, file share, object store | The arm64 Linux binary is already built and git is a Termux package, so this is a check rather than a port. Nobody has run it. |


## Two things the grid cannot show you

**A transport needs both halves.** The object store and the bundle are
implemented on your side and have no station side at all, so the combination
cannot work however the rows line up. That is why the transport table has a
column for each side rather than one status.

**Proven is not the same as written.** A station that has never run is a
template that looks authoritative, and shipping twenty of those would spend the
credibility of the ones that work. [The host contract](/hosts) is published so
you can judge an unlisted host yourself.

What might be added next, what is deliberately refused, and why, is on
[the roadmap](/roadmap).
