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 three 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. The axis is one thing: what is held, and for how long.
| beacon | A signal left where both can see it. You cannot reach the far side, the far side cannot reach you, and both can reach one agreed place. A message waits in the middle; nobody is ever connected |
| flare | Fired straight at a reachable endpoint. One burst, an answer, gone - a transaction, not a drop and not a standing line |
| beam | Held steady on the far station, live and two-way until it is torn down. The connection itself is what is held |
Everything shipped is a beacon. 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 beam is designed and not yet built, so nothing below carries it; the shapes are named together because the vocabulary is one thing.
The beam will need a compiled binary on the station, and almost nothing else does. Noise and a wss connection are not things curl and coreutils do, so the beam's station side is designed as a Go component. That is worth knowing before you plan around it, because the far side being plain readable bash is often the reason heliograph is permitted at all.
What needs a binary today is one transport, on one station: the relay, on a bash station, which shells out to heliograph-seal for the encryption. git, a file share, a bundle and an object store need none, the PowerShell station needs none even for the relay, and a beacon or a flare over any of those is a complete product - the same requests, the same gates, the same logs. An estate that permits no compiled code loses two shapes, not the tool.
There is one such binary rather than one per feature, and that is deliberate: anything else a bash station later needs signing or verifying is expected to reuse heliograph-seal rather than arrive as a second thing to audit and checksum. The list is station/FAR-SIDE-BINARIES, it is enforced in CI, and adding to it is a deliberate edit rather than a side effect.
The price of every far-side binary is a build you can reproduce and a checksum you can check for yourself, which is on provenance.
Why the shape matters more than the speed
The obvious difference is latency, and it is the less important one.
A beacon 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 a flare 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 flare page makes it in full; it is not a worse transport, it is a different security model.
A beacon 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 rather than built.
Pick what you have
Beacon: a dead letter drop. You cannot reach the far side, it cannot reach you, and both reach one agreed place. No inbound port, ever. More
Flare: you can reach the station directly, so the script travels WITH the request - and the mode header stops being a gate and becomes a claim the caller makes. More
| stationtransport | gitbeac | relaybeac | sharebeac | blobbeac | S3beac | bundlebeac | flareflar |
|---|---|---|---|---|---|---|---|
| Terminalbash, PowerShell | |||||||
| Dockerbash | |||||||
| Kubernetesbash | |||||||
| As a servicebash | |||||||
| Windows taskPowerShell | |||||||
| A pipelinebash | |||||||
| Azure containersbash | |||||||
| Azure VMbash | |||||||
| Function Appbash | |||||||
| Your own hostbash |
Nothing runs under those three filters together. That is an answer rather than a bug: loosen one and the grid comes back.
- Proven
- Works
- Needs a step
- Never checked
- Cannot
Every transport, in full
| Transport | Shape | Your side | The far side | What it is |
|---|---|---|---|---|
| git | beacon | 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 | beacon | proven | works | Both sides dial out over ordinary HTTPS. No git host, no storage account, no VNet. The bash station needs heliograph-seal beside it; the PowerShell one needs no binary at all - its seal is managed C# that ships as source. |
| file share | beacon | 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 | beacon | 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 | beacon | works | works | S3-compatible: AWS, R2, MinIO, B2, Spaces, Ceph. The station signs with SigV4 in bash, over openssl and curl - no AWS CLI and no binary. |
| bundle | beacon | works | works | The only thing that makes air-gapped literally true: a person carries the file, and no path between the two machines is needed at all. Both halves work; a round trip takes as long as somebody takes to walk. |
| flare | flare | 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, in full
| 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 flare 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, in full
| Controller | Status | Transports | What it is |
|---|---|---|---|
| The CLI, on Linux, macOS or Windows | proven | git, relay, file share, object store, bundle, flare | 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. |