Skip to content

Launch GraphCode Nod sessions and read their event log - #592

Merged
scgopi merged 8 commits into
mainfrom
feat/nod-launch
Oct 2, 2026
Merged

scgopi merged 8 commits into
mainfrom
feat/nod-launch

Conversation

@scgopi

@scgopi scgopi commented Oct 2, 2026 •

Copy link
Copy Markdown
Owner

Summary

Wires GraphCode Nod into the session machinery: graphcoded and the app launch graphcode-nod in zmx like every backend, and one fold over its events.jsonl gives the daemon presence, the live line, usage, summary beats and the goal verdict. Stacked on #583 (feat/nod-foundation, rebased onto 9766e57). Rebase onto main once that merges.

isSpiked stays false for .nod except when GRAPHCODE_NOD_PATH names a runtime. The flip, and putting Helpers/nod in the bundle through Project.swift, come in NodProvider's integration PR once #590 and this are on main.

Changes

  • Launch. executableName is graphcode-nod, resolved by absolute path (NodRuntimeLocator), never from PATH: GRAPHCODE_NOD_PATH, then Contents/Helpers/nod/graphcode-nod (app panes), then <support-dir>/bin/nod/graphcode-nod (graphcoded). nodArguments adds --node --cwd --engine --loop-type [--model] [--goal-file] [--inherit] [--unattended], and launchArguments adds [--briefing] --prompt. Resume is --resume <conversationID>. A main loop with no note still launches. Env is NOD_STATE, NOD_NODE_ID, NOD_PROJECT_PATH. Remote projects get no Nod launch yet.
  • App pane. GhosttyTerminalView.launchPrefix builds the same argv and env for .nod, so the pane and the daemon cannot disagree.
  • Install. DaemonBootstrap.installHelpers copies the bundle's Contents/Helpers/nod directory to <support-dir>/bin/nod (a copy, so a moved app keeps working). It is in the helper stamp, and optional, so bundles without it still install.
  • One fold. NodSessionFold (Domain) is the only reader of the event log. Needs you comes from an open permissionAsked, or a failure other than contextFull. It also gives the live line (asks to run … while waiting), usage as running totals, the goal verdict and progress, and NodCardState for NodSetup's cards.
  • Readers. NodSessionLog feeds presence (.reported), activity, usage, summary beats and GoalVerdictReader. recordsGoalVerdict is now true for Nod. It banks the live run's conversation id into SessionIDStore, so reboot, restart and app reopen all resume.
  • Input. NodControlClient speaks control.sock. sendInput, and so .message edges, node send and heartbeats, sends a queued send, falling back to typing the line when the socket isn't up yet.
  • Ramp. FeatureRamps.nod is mirrored into <support-dir>/nod/ramp.on on each app launch (like publishCodespaceMultiplexFlag). Without it, GraphStore and graphcode node create --backend nod refuse with "GraphCode Nod is not enabled on this install yet", and neither launcher resolves a runtime.
  • GraphStore. The goal poller now arms a verdict read for Nod despite its nil goalDirective; before this, a Nod goal loop never resolved. The live line is now also read for a Nod loop in Needs you.
  • Contract. NodProtocol.swift and PROTOCOL.md gained a "Launch" section (mailroom #1199, #1233). README's NOD_STATE path is corrected.
  • Testing aids. scripts/fake-graphcode-nod speaks the protocol without a model. scripts/nod-daemon-e2e.py drives an isolated graphcoded (own support dir and ZMX_DIR) through the CLI.

Left for whichever of this and NodGraphLayer merges second: inheritFile: node.lineage?.briefPath, and unattended for lineage.kind == .compositeChild in ZmxSessionLauncher.nodArguments. The NOD_STATE helper converges with #591's NodStateDirectory, and the second to merge deletes its copy.

Test plan

RED: xcodebuild test -only-testing:graphcodeTests/NodGraphStoreTests with GraphStore.swift from bd291d5 -> exit 65, aNodGoalLoopIsResolvedByItsRecordedVerdict expected state succeeded, found running; aNodLoopWaitingOnAHumanSaysWhatItAsks activity nil
GREEN: xcodebuild test -only-testing:graphcodeTests/NodGraphStoreTests -> pass, exit 0, 2 tests
REGRESSION: xcodebuild test -scheme graphcode on dda9433, private DerivedData -> pass, exit 0, 2038 tests in 223 suites

  • Focused Nod suites (fold, log reader, launch argv, control socket over a real unix socket, bootstrap install) pass.
  • python3 scripts/nod-daemon-e2e.py .spm/debug -> 20/20 PASS, exit 0. It covers creation refused while the ramp flag is absent, then the runtime launching, the launch argv, the goal file, node send arriving on control.sock as a queued send, reported idle and busy, an open ask showing as Needs you with "asks to run swift package resolve", usage, goalCheck met:true resolving the loop, a timed loop launched --unattended, a heartbeat landing in the same conversation, the conversation id banked, and restart resuming it with --resume and no prompt.
  • Gate on dda9433 (= pushed head), private DerivedData: graphcode-cli and graphcoded scheme builds exit 0. swiftlint has 0 errors, and swift-format lint --strict is clean.
  • Argv parity (backend-argv-parity skill): one dump test run on Add GraphCode Nod as a sixth backend kind, with its wire protocol #583's head (e9f7448) and on this branch, each with private DerivedData. It covers launchArguments with and without a prompt, resumeArguments, ZmxSessionLauncher.arguments/resumeArguments both local and ssh://, the app's launchPrefix/agentCommand/resumeCommand/sessionEnvironment/remoteCommand for goal, time and turn loops with and without a prompt, plus SummaryModelWriter.invocation and TitleSuggestionClient.invocation. For claudeCode, codex, copilotCLI, openCode and pi, 1,845 argv lines in 285 labelled shapes are byte-identical.
  • scripts/cli-smoke.sh against a separate SwiftPM build -> exit 0.

Checklist

  • I have read the Contributing Guidelines
  • I have signed off my commits (git commit -s) per the DCO
  • Tests pass locally (make test)
  • Code follows the existing style (make check)
  • I added the test/contract before the implementation (the tests came after; RED was observed by restoring the base GraphStore.swift)

scgopi and others added 5 commits October 1, 2026 22:18
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: scgopi <scgopireddy@gmail.com>
graphcode-nod runs in zmx like every backend: resolved from the app bundle
(GRAPHCODE_NOD_PATH overrides), launched with --node/--engine/--loop-type and
NOD_STATE, resumed with --resume <conversation-id>. One fold over events.jsonl
(NodSessionFold) answers presence, the live line, usage, summary beats and the
goal verdict; an open permissionAsked is Needs you. Messages go through
control.sock as queued sends.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: scgopi <scgopireddy@gmail.com>
The goal poller armed a verdict read only for backends with a typed directive,
so a Nod goal loop never resolved; and the live line was read only for busy
sessions, so a loop waiting on a permission never said what it asked. Both now
include Nod.

Also NOD_PROJECT_PATH/NOD_GRAPH_FILE and --inherit for the graph layer, the
README's NOD_STATE path, tests for the fold, readers, argv and control socket,
a fake graphcode-nod, and scripts/nod-daemon-e2e.py driving an isolated daemon.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: scgopi <scgopireddy@gmail.com>
graphcoded runs from the support directory with no bundle, so the app now
copies Contents/Helpers/nod to <support-dir>/bin/nod with its other helpers
(copied, not linked, so a moved app keeps working). Timed loops launch
--unattended; nodArguments takes --inherit for fresh launches. The session
env carries NOD_NODE_ID and NOD_PROJECT_PATH instead of a graph file path.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: scgopi <scgopireddy@gmail.com>
FeatureRamps.nod is the app's; graphcoded and the CLI have no ramps. The app
now mirrors it into <support-dir>/nod/ramp.on on each launch, as it does the
codespace multiplex flag, and without it node creation is refused with a
reason, and neither launcher resolves a runtime.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: scgopi <scgopireddy@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: scgopi <scgopireddy@gmail.com>
@scgopi
scgopi changed the base branch from feat/nod-foundation to main October 2, 2026 14:50
Signed-off-by: scgopi <scgopireddy@gmail.com>
Signed-off-by: scgopi <scgopireddy@gmail.com>
@scgopi
scgopi merged commit 5839961 into main Oct 2, 2026
25 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant