bot-crossing README

The project's README from github.com/Station-Sciences/bot-crossing. As one markdown file: README.md.

Bot Crossing — your agent threads, as a colony

botcrossing.com

Every coding-agent thread on this machine is a little astronaut. They walk out of the ship, claim a plot for their repo, and build something. When one needs you it stops and holds a ? over its head; click it and the thread opens back in whichever harness it came from.

It reads the harness's own files, on your own machine. Nothing is uploaded, there is no account, and it never writes to a harness at all — data/colony.json, where the map lives, is the only file it writes anywhere.

Status: published as-is. I built this for myself and cannot promise to maintain it — issues and PRs are welcome but may go unanswered, and forking is an entirely reasonable thing to do. CONTRIBUTING.md sets out what to expect.

Run it

npm install && npm run dev

Needs Node 22.13 or newer. npm test runs the suite.

npm run dev is the whole thing: the API lives inside the Vite dev server, so there is no second process. For a built version, npm start (build + serve) or npm run serve if dist/ already exists. Binds to 127.0.0.1 by default, and answers only its own page — see Keeping it local.

macOS, Linux and Windows. Opening a thread, revealing a folder and starting a new session all go through a harness:// deep link handed to the OS opener — open(1) on macOS, xdg-open on Linux, ShellExecute on Windows. The scanning half was portable already. On Linux, where a desktop app often is not installed, the scheme is checked first and a terminal running the harness's own CLI opens instead when nothing answers it.

Which harnesses work

A harness is whatever actually runs your threads. Bot Crossing reads each one's local session files through a small adapter, so support is per-harness and mostly a matter of somebody writing that adapter.

Harness Status
Claude Code (Anthropic) ✅ Supported — desktop app and CLI, including worktrees and live-process detection
Codex (OpenAI) ✅ Supported — desktop, VS Code and CLI sessions, opened through codex://
OpenCode ⬜ Not yet
Antigravity CLI (Google) ⬜ Not yet — the successor to Gemini CLI, which Google stopped serving individual accounts on 18 June 2026
Cursor (Anysphere) ✅ Supported — agent transcripts; the composer/sidebar threads are not read yet
Amp (Sourcegraph) ⬜ Not yet
Aider ⬜ Not yet
Goose (Block) ⬜ Not yet
Qwen Code (Alibaba) ⬜ Not yet
Amazon Q Developer CLI ⬜ Not yet

Every harness that is installed shows up at once — the colony is the union of all of them, and an astronaut carries the name of the harness it belongs to.

Adding one

One new file in server/harnesses/, one line in its index.mjs, and nothing else. The interface is small and written down in full, along with the thread shape, the ground rules, and how to find where a given harness keeps its sessions:

→ server/harnesses/README.md

If you add one, a PR is very welcome — see CONTRIBUTING.md first, which is honest about how much support I can offer. If landing it means editing the scanner or anything under src/, please mention that: it means the seam needs widening, and I would rather fix that than have you work around it.

What you are looking at

In the colony In your threads
One hex zone One repo. Bigger repos claim more tiles — one per seven threads, grown as a contiguous blob from the middle outward. A zone stays where it is: see below
One astronaut + one building One session
How finished a building looks How large its transcript is, on a log scale
Scaffolding Somebody is at that site right now
Walking out of the ship A thread that just appeared
Walking back into the ship You archived it

A zone stays where it is

The map is only useful if you can learn it, so the layout is sticky. The previous arrangement is an input to the next one: a repo that still needs the same number of tiles keeps exactly the tiles it had, one that grew keeps them and claims neighbours, and one that shrank gives back whatever it claimed most recently — so growing and shrinking again returns a zone to precisely the shape it started in. Only a repo that has never been placed is placed at all, and it takes the innermost tiles still free.

A zone's origin is its root tile rather than the centre of the tiles it happens to hold, so gaining one does not drag its buildings, its crew and its name sideways; the new tile simply appears alongside. And the arrangement is written to data/colony.json, so the map you have learned survives a reload — including for a repo whose last thread you archived, which comes back to the same ground when you start a new one.

The version before this was a pure function of the thread counts: one session appearing anywhere changed the sort order, the order decided the tiles, and the whole colony re-laid itself out. A zone you were watching could jump to the far side of the map because a different repo gained a thread.

The deck has to clear the ground

Inside the colony the terrain is gentle but not flat — it runs from about -0.3 to +0.24 on the Moon, and half again as far on Mars. A deck's top face has to sit above the roughest ground any plot can be dealt, or the ground comes through it: the slab reads as sunken, props standing on it are buried to the waist, and every surface where the two meet tears. So the slab is 0.45 tall, and everything on a plot — buildings, kerbs, clutter, boots — is measured from that one number rather than from a height of its own.

Ground scatter has to miss the plots, and the order it happens in is the whole problem: the world is built before the first roster arrives, so at that moment there are no plots to miss. Boulders and trees would end up under decks laid on top of them afterwards, poking through in fragments. The scatter is therefore rebuilt whenever a zone's footprint changes — cheap, because it no longer drags the terrain mesh along with it.

Astronaut behaviour is a strict precedence rather than a set of independent flags, so a thread can only ever be doing one thing. First match wins:

Signal What the astronaut does Badge
Errored Slumps, red eyes, fault light stutters !
Running now Hammers away at its building, sparks fly ⚒
PR merged Jumps, confetti, heart eyes ✓
Unread Stops and waits on you ?
Nothing for 3 days Sits down and sleeps, z bubbles —
Anything else Potters around its plot —

Only the states that want something from you get a badge. With most of a real thread list sitting quiet, a symbol over every astronaut buries the one ? that actually matters.

Zone names follow the same rule: a plot shows its name only while somebody there is working, waiting or stuck. Everything else is nameless until you point at it. The plate itself is just text over a soft halo with a small accent dot — no panel, no outline.

Getting about

Astronauts route rather than drift. Buildings and the landing pad are rasterised into a navigation grid whenever the roster changes, and the crew walks it with A*, string-pulled afterwards so they take the corners they actually need instead of a visible staircase.

Two guarantees, deliberately independent:

Measured over the live colony: 288 path legs, 0 crossing a building, and 0 penetrations across 78,000 agent-frames. A typical path costs 6 µs (most are a clear straight shot and skip the search); the worst frame when a poll invalidates every route at once is 0.6 ms.

They also push each other apart, so a busy plot is a crowd rather than a pile. That spacing is measured against the widest thing an astronaut wears — the helmet, at 0.95 units — because holding a crowd at less than that is a crowd standing inside itself, which is what the first version did at 0.72. Arrival is derived from the same number and is deliberately larger: an astronaut that had to get closer than its neighbours would let it could never finish arriving, and would shoulder at the crowd for as long as its thread existed.

Standing spots are placed clear of the building's own blocked radius rather than at a fixed distance from it, and checked against the navigation grid — a spot inside a wall is a spot the crew can never reach, and the astronaut sent to it walks at that wall forever. Measured over the live colony: 68 of 68 astronauts settled, nobody closer than 1.14 units, no standing spot left inside an obstacle. As a last resort an astronaut that has been blocked for six seconds adopts the ground it got to instead of pushing on.

Clicking one

All of the chrome is one panel on the right — the name, the counts, and every repo. There is no top bar and no strip along the bottom: a colony is a place, and a place reads better without a frame around it.

An astronaut, a zone's deck, the name plate over it, or a repo in that list — all four drill into the same repo. Picking somebody is also picking the zone they are standing on.

The repo, at the top, whether or not anybody is selected:

The thread, when an astronaut is selected, in a card parked beside that astronaut rather than in the panel: its face, title, worktree, branch, model, last activity, and how far along its building is. The answer to "what is this one doing" belongs next to the thing you clicked, so the card follows its astronaut around the screen — preferring its right, flipping to its left rather than sliding under the sidebar, and never leaving the window. It is moved with a transform rather than with left/top, the one geometric change a browser makes without touching layout, so following a walking astronaut costs nothing.

Quiet repos fold away by default: a repo where every thread has been silent for three days comes off the map, which on a machine with a few harnesses and a lot of checkouts is most of them. The sidebar keeps a count and a way back, and a repo returns to its own ground the moment a thread in it wakes up. Hide dormant repos in settings turns it off.

Only one button in the panel is ever the accent colour: whichever action is the immediate one. Esc steps outward a notch at a time — the thread first, then its zone.

Opening uses claude://claude.ai/epitaxy/<local_…>, which navigates the desktop app to a thread it already has. claude://resume is the fallback for threads that only exist as a CLI transcript: it imports the transcript, which creates a second untitled session and rewrites the .jsonl, so it is only ever used when there is nothing to navigate to.

Archiving carries a deliberate one-writer discipline: the browser owns data/colony.json and PUTs it whole, /api/archive only touches Claude Code's records. If both wrote it, a save from a page holding older state would silently drop every archive made since that page loaded. Claude Code also rewrites its session records from memory and can stomp the flag, so the colony re-asserts it on every scan — an archive that gets stomped comes back within one poll.

Nothing is ever written to your Claude Code data except that one isArchived field. The folder buttons only ever hand a path to open.

The deep links above are the Claude Code adapter's business, not the colony's — another harness plugs its own in, and a harness with no deep link simply greys the button out. See server/harnesses/README.md.

Plots are keyed by the folder's name, which is all the colony needs to draw one, so the path is read back off the threads standing there. Where a name is ambiguous — ~/workspaces/1/foo and ~/workspaces/2/foo, which is what you get keeping parallel copies instead of worktrees — it grows leftward until it is not, and you get 1/foo and 2/foo on separate ground. Only names that actually collide change, because the name is also the key your saved layout is stored under and disambiguating everything would move every plot on the map. A repo that has moved or gone since the last scan fails at the server rather than handing open a dead path.

Name plates are hit-tested in screen space rather than raycast: they are billboarded in the vertex shader, so a raycast would test the quad where it was authored rather than where it ended up. That test deliberately ignores whether the plate is currently faded in — pointing at where a quiet project's name would be is exactly what makes it appear.

Getting around

Navigation is Google Earth's, including both of the things that make Earth feel like Earth:

Drag Grabs the ground. The point under your cursor stays pinned there for the whole drag
Right-drag (or ⌃ / ⇧ / middle-drag) Tilt and rotate. Up tilts toward the horizon
Scroll Zooms at the cursor, not at the screen centre
Two fingers Pinch to zoom, drag to pan — both anchored between your fingers
Arrows, + / − Move and zoom from the keyboard
Orbit mode (rail button, or O) Earth's auto-rotate: a slow sweep around whatever is centred, about two minutes a revolution. It drives the heading only, so you can keep dragging, tilting and zooming while it runs

Both anchors are exact, not approximate: a 200px drag holds its grabbed point to 0.00 world units, and dollying 62→37 holds the cursor's point to 0.01.

Optionally, letting go can ease the angle back to the nearest clean isometric heading after a couple of seconds. Your position and zoom are never touched — going home on its own would fight you; tidying the angle after you stop does not. It is off by default, because a camera that moves when you did not ask it to is startling the first time you meet it. Turn it on under View → Return to isometric.

Keys

Key Does
H / ⌘\ Hide every panel. The colony still reads: status lives above the astronauts' heads
S Settings
N Fly to the next astronaut waiting on you
Enter / A Open / archive the selected thread
V Mark the selected thread viewed, so it stops asking
C New conversation in the open zone's folder
O Orbit mode
Tab Next planet
L Next time of day
P Screenshot
0 Reset the view
Esc Deselect, and close the zone sidebar
? Help

Planets and light

Three worlds — Luna, Mars, Terra — and a full day/night cycle you can scrub, let run, or set to Live, which follows this machine's own clock so the colony's light matches the light out of your window. A planet is a bag of colours and two switches; terrain, scatter, sky and lighting all read from the same preset, so a fourth world is a data change rather than a code change.

The sky is the HDRI

Rather than shipping an HDR environment map, the sky shader is the environment map. A second copy of the sky dome — sharing the same uniforms, so it is always the sky you are actually standing under — is rendered into a prefiltered radiance map with PMREMGenerator and bound as scene.environment. That is what gives metal something to reflect and dielectrics a directional ambient, and it is why the colony changes character through the day rather than just changing brightness: at dusk on Mars the panels pick up the sky, on the Moon they stay hard and neutral.

It regenerates only when the sky has actually moved, and never more than a few times a second. Measured cost: 0.16 ms/frame. Off on Potato and Low; the intensity is a slider.

Materials are properly PBR underneath it. Roughness and metalness are looked up per atlas cell, so a single merged building geometry holds painted panel, brushed metal and photovoltaic glass and each behaves correctly — the ten building recipes never had to learn about PBR.

The sun is not overhead

The solar arc is tilted, so noon puts the sun 54° above the horizon and off to one side rather than at the zenith. That is load-bearing rather than decorative: a sun directly overhead puts N·L at zero on every vertical wall in the colony, and they go black with only ambient to catch. The old procedural buildings were curved enough to hide it; a kit of flat-walled modules is not.

HDR and bloom

Eye colours, lamps, windows, crop rows and plot kerbs are all authored above 1.0 so the bloom pass picks them out. The threshold is deliberately high (0.92) — only those things clear it, so lit surfaces stay crisp instead of going hazy.

Where the art comes from

The colony is built out of two CC0 asset packs by Kay Lousberg, plus the project's own shaders on top of them.

Pack Used for Licence
KayKit : Space Base Bits Every building, the landing pads, rovers, and the crates and drums stacked around each plot CC0
KayKit : Character Animations The crew's body and all fifteen animation clips they play CC0
KayKit : Forest Nature Pack Terra's trees, bushes and grass, and the boulders on every world CC0

CC0 asks for nothing, but crediting Kay costs noth

bot-crossing (Station-Sciences/bot-crossing)

Searching Database…

这是什么?Bot Crossing 是一个本地 3D 可视化,把你机器上的每个 AI 编程智能体会话都变成聚居地里的一名小小宇航员:每个仓库变成一个六边形区域,每个会话/线程都是一名在上面施工的宇航员。
这是游戏还是监控面板?它号称是游戏,但行为更像一个实时监控面板:一个在浏览器中渲染的 Three.js 场景,由本地 Vite 开发服务器提供,实时反映你的编程智能体线程的真实状态。
怎么让它跑起来?克隆仓库后运行 'npm run dev' 获得完整的开发体验,或运行 'npm start' 构建并提供生产版本;它只绑定 127.0.0.1。
运行之前需要安装什么?只需要 Node.js 22.13 或更新版本;没有其他需要设置的服务或账号。
它实际支持哪些编程智能体?Claude Code 和 Codex 获得完整支持(桌面应用、CLI、VS Code);支持 Cursor 的智能体转录,但不支持其 composer/侧边栏线程;OpenCode、Antigravity CLI、Amp、Aider、Goose、Qwen Code 和 Amazon Q Developer CLI 尚未实现。
它能运行在哪些操作系统上?macOS、Linux 和 Windows 都支持,并针对不同系统做了专门处理,以便把线程重新打开到对应的宿主应用中。
它会把我的会话数据上传到别处吗?不会——它只读取你自己机器上的宿主文件,从不回写任何宿主,唯一会写的文件是本地地图状态。
点击宇航员会发生什么?会打开一张显示该会话详情的卡片,并提供选项:在对应的宿主中恢复它、显示其文件夹、新建会话、标记为已查看或归档;打开时使用 claude:// 或 codex:// 之类的深度链接(Linux 上则以终端方式兜底)。
地图如何决定每个仓库区域的位置?区域位置是“粘性”的:仓库会保留自己原有的地块,只在需要更多空间时向邻近地块扩展,并从最近新占的地块回缩,因此布局在多次刷新之间依然容易辨认。
不同的宇航员动画是什么意思?每个宇航员的行为按优先级顺序反映其线程的一种状态:出错(红眼、故障灯)、正在运行(挥锤敲打并冒火花)、PR 已合并(跳跃、撒彩带)、未读(停下并举着一个“?”)、闲置超过 3 天(睡觉)。
我可以为未列出的编程智能体添加支持吗?可以——新增一个宿主只需要在 server/harnesses/ 里添加一个新文件,并在它的 index.mjs 中加一行;适配器接口记录在 server/harnesses/README.md 中。
Size
9 MB
Stars
822
Updated
CommitSep 18
Age
1mo
Commits · 90 Days