Releases: scgopi/GraphCode
Release list
0.1.62
0.1.62-beta3 — the Artifactory is now the Mailroom
The shared board is now the Mailroom, and it says what it carries.
A room, and what passes through it
"Artifactory" named a storage place. What the board actually holds is correspondence, so the vocabulary now matches:
- Letters — traffic addressed to someone:
node send, message edges, handoffs. - Notices — posts addressed to nobody, for whoever comes next.
The CLI reads that way too:
graphcode mail post | inbox | read | list | watch
inbox replaces sync as the name for "what have I not seen", which is what it always did.
The Mailroom now takes half the rail, and a letter's budget rose from 50 to 200 characters — enough for a real handoff rather than a truncated one.
Nothing you have is lost
A rename that stranded an existing install's board would be a poor trade for better words, so every old spelling is still read:
| What | Old key |
|---|---|
| The board itself | artifactory |
| Your off-switch | artifactoryEnabled |
| A loop's cursor and watch | lastArtifactoryRead, artifactoryWatch |
| Both post kinds | their pre-rename spellings |
| The ramp key | as it shipped |
Old keys are read and never written, so the old spelling does not outlive the rename in every file the app touches. A post whose kind is unrecognised is read as a notice rather than failing the decode and taking the board with it.
graphcode artifactory … and graphcode … sync still work, as undocumented aliases. Every loop running right now was briefed with those words, and they keep working — verified end to end against a live board with the shipped binary, not only in tests.
Verified on the merged tree: 1585 tests · 163 suites · 0 failures · swiftlint 0 errors · swift-format clean. Seven tests pin the old spellings, each observed executing. Bundle identity verified dev.graphcode.app in both the Info.plist and the code signature.
0.1.62-beta2 — a goal loop that arms its own stop condition
A goal loop now tells its agent when to stop in the agent's own language.
The stop condition, as a directive
A goal-based loop opened its session with prose — "Work toward this goal until it is met: …" — and hoped the agent kept it in mind. Time-based loops already had better: they arm /loop, a real directive the agent acts on.
Goal loops now get the same treatment. A session opens with the backend's own /goal <condition>, which sets a session-scoped stop condition rather than a sentence the model has to remember it read.
All four backends carry it — Claude Code, Copilot CLI, Codex and OpenCode — each with the directive read off the real CLI rather than assumed.
The prose has not been deleted. It stays as the fallback for any backend that has no directive of its own, so nothing regresses if a backend is added without one.
Why this is more than cosmetic
A stop condition the agent merely read competes with everything else in the context; one it armed does not. This is the same reasoning that put /loop behind time-based loops, applied to the other half of the loop types.
Verified on the merged tree: 1578 tests · 162 suites · 0 failures · swiftlint 0 errors · swift-format clean. Bundle identity verified dev.graphcode.app in both the Info.plist and the code signature.
Carried forward from 0.1.62-beta1: the Artifactory's five verbs in the remote shim (#281/#282). Its watch item still stands — cliShimStamp is content-derived, so if you have not yet installed a 0.1.62 build, the first remote ensure after this propagates the new shim to every remote host, and that delivery path has had no end-to-end run on a real remote or Codespace.
0.1.62-beta1 — the board, from a remote loop
Opens the 0.1.62 line. The Artifactory is reachable from remote and Codespaces loops.
The board, from a remote loop (#281)
A loop's briefing has always taught five Artifactory verbs — sync, list, read, post, watch. On a remote host or in a Codespace, all five were refused: the remote shim graphcode delivers had never learned them, so a loop was told to use a board it could not reach.
The shim now implements all five, rendering exactly what the Mac's CLI renders.
Why "exactly" is the whole point
The shim is a second implementation of renderers that already exist in the Swift CLI, and a defective one propagates to every remote host on the next ensure. So parity is verified rather than assumed:
- Its tests assert against the production Swift renderer called on the same fixture, never against a string written by hand — that is the drift guard.
- Beyond the fixtures, the delivered shim was diffed against the real CLI on a live board across four verbs and ~115 KB of output, byte for byte.
That diff matters because Swift and Python disagree quietly in ways plain-ASCII fixtures never reveal: Swift's JSONEncoder escapes forward slashes as \/ and emits non-ASCII raw, where Python's json.dumps does neither. The compared output carried 312 escaped slashes and 134 non-ASCII characters — em dashes, arrows, ⌘, and CJK — and matched exactly.
⚠️ Watch the first remote ensure
cliShimStamp is content-derived, so the first ensure after installing this propagates the new shim to every remote host you use. That propagation path was proven by parity testing, not by an end-to-end run against a real remote host or Codespace.
If you run remote loops, watch the first ensure once. Everything above verifies that the shim renders correctly; nothing yet verifies the delivery on a live remote.
Also in this build
A flaky test was fixed rather than retried. MessageDeliveryTests drives node send against a real zmx session, and its sink wrote results with a call that truncates the file on open — so a reader polling for the file could read it empty and compare a short string against the whole message. It writes aside and renames atomically now, and waits as long for the result as it already waited for readiness. The suite failed on a different test each run and never in isolation, which is what a race looks like from the outside.
Verified on the merged tree: 1574 tests · 162 suites · 0 failures · swiftlint 0 errors · swift-format clean. Bundle identity verified dev.graphcode.app in both the Info.plist and the code signature.
0.1.61
What's Changed
- Make local development identity configurable by @francip in #269
- A loop-created loop starts on the human's default backend (#270) by @scgopi in #271
- Codex readiness waits on a label graphcode writes (#272) by @scgopi in #275
- Type a long message as a paste, not as a burst of keystrokes (#277) by @scgopi in #279
- Fold a CLI-created loop's name into one word by @scgopi in #280
New Contributors
Full Changelog: v0.1.60...v0.1.61
0.1.61-beta3 — one word, wherever the name comes from
A loop named from the command line now looks like every other loop on the canvas.
One word, wherever the name comes from
Loop names are read in a sidebar row and a card header, where one word carries further than two. The backend namer has asked for that shape for a while and folded what it got back — so a loop it named became BoardVisibility, not Board Visibility.
A name written by hand skipped that fold entirely. graphcode node create --title "Board Visibility" kept the space, and a loop fanning work out to its children produced a canvas of two kinds of name.
Now the same fold applies wherever a name arrives already written:
--titleon the CLI is folded —"Board Visibility"becomesBoardVisibility.- The session briefing asks for that shape, since telling is all an instruction can do when a loop is writing the name itself.
- The remote Python shim folds too, so a loop created on a remote host is named the same as one created locally.
Punctuation is trimmed and words are joined in CamelCase. A title with nothing alphanumeric in it is kept exactly as given rather than becoming a nameless card.
Verified on the merged tree: 1561 tests · 162 suites · 0 failures · swiftlint 0 errors · swift-format clean. #280's own test observed executing. Bundle identity verified dev.graphcode.app in both the Info.plist and the code signature.
Everything in 0.1.61-beta2 — the Codex readiness fix (#272) and whole-message delivery for node send (#277) — is carried forward.
0.1.61-beta2 — Codex loops attach again
Two fixes to the way a loop's session is reached: Codex loops attach instead of timing out, and a long message sent between loops arrives whole.
Codex loops attach again (#272)
Every Codex loop's pane spun for 60 seconds and then dropped you into a bare login shell. The pane waits for the session to be ready before attaching, and for Codex — and only Codex — that wait included a second condition: find the agent's name in the session's cmd= field, as reported by zmx ls.
A loop's session is created by zmx run, and zmx ls never reports a cmd= for one. The condition could not be satisfied, so the gate polled ten times a second until it hit its 600-try cap, gave up, and fell back to a shell.
The gate now waits on a readiness label graphcode writes itself rather than on a command zmx was never going to record. Being graphcode's own data, it cannot drift with another tool's output format.
There is a decoy worth knowing about if you go reading: zmx ls does truncate a long cmd=, which looks like the cause and is not — a loop session has no cmd= to truncate in the first place. The fix's own comments name it, and a new CodexReadinessGateTests suite pins the behaviour.
This has been present since 0.1.57-beta5, so it affects the current stable as well as the beta line.
A long message between loops arrives whole (#277)
graphcode node send typed its message as a burst of keystrokes. A long one could lose its head — the opening characters landed before the receiving session was listening for them. It is now delivered as a paste, which arrives as one unit.
Its delivery suite also stops passing vacuously when zmx is absent: it skips, and says so.
Updating
This release changes the session launch path, so the daemon changes too. Nothing manual is required — on launch the app compares a stamp of its bundled helpers, re-stages graphcoded when it differs, rewrites the launch agent and reloads it, carrying the update to closed workspaces as well.
The one thing to know is when: that happens on relaunch. Choosing "Later" at the relaunch prompt leaves the new app running against the old daemon until you do relaunch.
Verified on the merged tree: 1560 tests · 162 suites · 0 failures · swiftlint 0 errors · swift-format clean. CodexReadinessGateTests, MessageDeliveryTests and RespawnOnSendTests each observed executing. Bundle identity verified dev.graphcode.app in both the Info.plist and the code signature.
Not in this cut: #273/#274 (board discussion, parked pending review), #276, #278.
0.1.61-beta1 — your default backend, everywhere
Opens the 0.1.61 line. A loop that creates a loop now respects the agent you actually chose, and a development build can finally run beside your installed copy.
Your default backend is the default everywhere (#270)
Settings → Sessions has always picked the agent a new loop runs. It just wasn't consulted when a loop created the loop: a creating loop passed its own backend down, and when there was nothing to inherit from — a human's shell, or a session fanning out into another project — the draft fell through to a hardcoded Claude Code.
The result was quietly wrong in a way that was easy to misread as flakiness: a human running Copilot got Copilot loops everywhere except the ones their own loops created.
Now the setting is read fresh at each creation, so changing the picker applies to the very next loop. The session briefing also teaches --backend, so a loop knows it can choose deliberately rather than inherit by accident.
A dev build that runs beside your release
Contributed by Franci Penov. Testing a local build used to mean replacing the copy in /Applications. Two builds could not coexist, for two independent reasons — and make dev-* now fixes both together, because fixing either alone still leaves you broken:
- A distinct bundle id. macOS keys Login Items, LaunchServices' "which app owns this?", and background-agent attribution off it.
- A distinct support directory. Otherwise the two share graphs and zmx session names, and fight over the same sessions.
The dev daemon gets its own launchd label for free, so nothing touches the release's agent. make dev-status tells you what your dev build actually is when the two get confusing, and make doctor now checks Tuist dependencies too.
Identity lives in an optional .env / .env.local (see the committed .env.example); release builds are unaffected and continue to ship as dev.graphcode.app.
Note on updating
This release changes the daemon (ProjectRegistry, GraphStore), not just the app. Nothing manual is needed — the app re-stages its helpers and reloads the agent on launch — but that happens when the app relaunches. If you click "Later" at the relaunch prompt, you will be running the new app over the old daemon until you do.
Verified on the merged tree: all three schemes build Release · swiftlint 0 errors · swift-format clean. Bundle identifier verified dev.graphcode.app in the shipped build.
0.1.60
0.1.60-beta1 — export and import, without the switch
Opens the 0.1.60 line. Exporting and importing loops is now simply a thing GraphCode does, and loops get shorter names.
Export and import, without the switch
Exporting a loop and importing a bundle no longer wait on a setting. graphcode node export and graphcode node import work out of the box, as do the menu items — and the Settings toggle that used to gate them is gone rather than left on by default. The flag it was built on is removed entirely, not stubbed: nothing in the codebase still asks whether sharing is allowed.
If you ever hunted for "Export and import loops" in Settings to explain why the CLI refused, that page is one row shorter now and the refusal is gone.
Loops are named in one word
- A new loop gets a single-word name instead of a phrase.
- Placeholder and instance names are folded into one word too, so what you see while a loop is being created matches what it settles on.
Known rough edge
The CLI's own --help for node export / node import still tells you the verbs stay off until "Export and import loops" is enabled in Settings. That sentence is now wrong — the verbs are unconditional and the setting does not exist. The behaviour is right; only the help text is stale, and it is fixed separately.
Verified on the merged tree: 1538 tests · 160 suites · 0 failures · swiftlint 0 errors · swift-format clean · all three schemes build Release.
Numbering note: this is 0.1.60-beta1, not 0.1.59-beta4. Stable 0.1.59 has shipped, and the updater ranks a stable above any beta of the same base version — a 0.1.59-beta4 would never be offered to anyone.