Skip to content

feat(go): first slice of full Go port of sumctl (#94) - #95

Merged
douglasjarquin merged 11 commits into
mainfrom
sum-dev/issue94-go-port
Sep 11, 2026
Merged

feat(go): first slice of full Go port of sumctl (#94)#95
douglasjarquin merged 11 commits into
mainfrom
sum-dev/issue94-go-port

Conversation

@douglasjarquin

@douglasjarquin douglasjarquin commented Sep 10, 2026

Copy link
Copy Markdown
Owner

Summary

First reviewable slice of #94: a full Go/Cobra port of sumctl to replace the Python CLI. This is owner-authorized and separate from #38/#39/PR #90 — those correctly closed a narrower, benchmark-gated prototype as a no-go; this issue and PR do not reopen or reverse that decision, and PR #90 is untouched.

Ports so far (command-for-command parity, one command's worth of Python behavior at a time):

  • release-contract (go/internal/contract): pure, read-only, no state/subprocess dependency — the first native command.
  • Order-preserving JSON I/O (go/internal/ordjson): lib/sumctl.py's atomic_json/read_json round-trip Python dicts preserving insertion order; Go's encoding/json can't do that for arbitrary maps, so this adds an Object type plus an encoder matching json.dumps(value, indent=2, ensure_ascii=True) byte-for-byte (2-space indent, empty containers stay inline, non-ASCII escaped to \uXXXX with surrogate pairs for astral characters).
  • store.Store (go/internal/store): ports Store.__init__/init/lock/path/read/save/all — task ID validation, symlink refusal, flock-based exclusive locking, sorted task listing. designated/owner/registration (the Herdr-session bits) are not yet ported; nothing needs them yet.
  • settings show (go/internal/settings, go/internal/pyrepr): ports capacity_view/load_settings and everything they validate (capacity, worker, presets, reviewer, launch-value/adapter checks). Error text is byte-identical, including Python repr()-formatted values in messages — that's what pyrepr is for.
  • preset list/preset show NAME: ports preset_list/preset_show, reusing load_settings/validate_preset_referencepreset_launch (the exact argv dispatch --preset would append) and preset_references (worker/reviewer default lookups).
  • Fix: error-JSON separators/escaping (go/internal/ordjson's new MarshalCompact, cmd/sumctl-go/main.go): the compiled entrypoint's top-level error handler used encoding/json's compact defaults ({"error":"x"}); lib/sumctl.py's matching path is a plain json.dumps({"error": str(exc)}), whose default (non-indent) separators are (', ', ': '){"error": "x"}. Found while differential-testing preset show's error path; would have broken byte-for-byte parity for every native command's error output, not just this PR's.
  • graph config --harness NAME [--raw] (go/internal/graph): ports graph_config/graph_tool — locates the pinned codegraph binary (SUM_CODEGRAPH_BIN override, else <runtime>/.local/bin/codegraph derived from the reference helper's own path, so this doesn't need the full ROOT/SUM_INSTALL_ROOT resolution machinery), probes --version, and prints the per-harness MCP snippet (claude/cursor/opencode as JSON, codex as TOML) or, with --raw, just the bare snippet. graph status/graph init are task-scoped (need a real task record) and stay on the Python reference; an invalid --harness also falls back to compat so Python's own argparse choice-validation/usage text stays exact rather than being reimplemented.
  • metadata snippet [--raw] (go/internal/metadata): ports metadata_snippet — the static config.toml text for sum's sum_* sidebar tokens plus the exact enable/inbox commands to run next. command_for shells that out through Python's shlex.join, so this adds go/internal/shquote (Quote/Join/CommandFor matching shlex.quote/join exactly) rather than hand-formatting a path that might contain spaces. enable/disable/status/sync/inbox and anything else stay on the Python reference.
  • Foundation: Store.designated/owner/registration/register/registrations (go/internal/store): registration-key hashing, identity-mismatch detection, registered_at preserved across re-registration. init (below) now reuses Designated; owner/registration/register still await the designated/coordinator branch this PR does not implement.
  • init (go/internal/store/context.go, go/internal/roleinit): ports context()/session_from_env() and init()'s non-designated branch only — installation-hint (is this checkout a linked worktree of a real sum installation?), matching-task (is this pane a dispatched worker's own pane?), and the development marker, assembled into the exact role/home/task/installation_home/note shape. The designated/coordinator branch — live pane verification, coordinator claim/reclaim, contract state, pump, hook/metadata summaries — is deliberately not ported: it's sum's most safety-critical path, and a bug there could misfire a coordinator claim or lose a registration. init only goes native once the store is confirmed not designated; every other shape (a designated installation, an unrecognized --role) falls back to the Python reference unchanged.
  • Docs (owner-directed, riding in this PR): ATTRIBUTIONS.md (issue docs: add a concise ATTRIBUTIONS.md and upstream-first README link #56) — a credit page for the projects that shaped sum, with the four explicit mappings the issue calls for (Firstmate → agent distro, Oh My Pi → builtins, Solo → meta-harness, Unpeel → MCP pane/session management) called out at both ends of the page. Linked from README's inspiration blurb and CONTRIBUTING's existing attribution checklist item. Not part of the Go port; included here because the boss asked for it in the same PR while this branch was already the active one.
  • doctor (go/internal/doctor, go/internal/toolpath, go/internal/herdrclient): ports the full aggregator — tool() lookups for python3/node/git/gh/herdr/quota-axi/lsof (env override, then the pinned <runtime>/.local/bin/<name>, then PATH), herdr --version/ensure_version, the herdr-context pane-get probe (a small JSON-RPC-style client around the herdr binary matching lib/sumctl.py's herdr()/run() error text for timeouts/nonzero-exit/non-JSON), the gh pr edit --help --attach probe, harness detection over the same HARNESSES table, the Mesh-installed marker check, and codegraph (reusing graph.Tool directly). Reuses Store.Designated/Owner/Registration for the role check. doctor is fully read-only ("never binds") so needed no scope reduction the way init did. Exit code now follows value["ok"] (0 when every check passes, 1 otherwise), matching the Python dispatcher's special case for this one command instead of the default always-0.

Native handling is intentionally narrow right now: each command only takes the native path for the exact invocation shape shown above with an explicit --home (e.g. settings set, preset delete, and everything else still shell out to the Python reference unchanged via root.go's compatibilityCommands fallback). Default --home resolution (mirroring resolve_installation()/SUM_HOME) isn't ported yet, so native commands currently require an explicit --home.

Test plan

  • go build ./... && go vet ./... && gofmt -l . clean inside go/.
  • go test ./... — all packages pass, including differential tests that run the real Python bin/sumctl and assert byte-identical stdout/exit codes against the Go binary: release-contract; settings show across 7 scenarios (empty store, populated settings, invalid capacity value, unknown preset reference, unknown capacity key, a preset arg conflicting with its own resolved model flag, task occupancy across active/archived tasks); preset list/preset show across 4 scenarios (empty store, multiple presets with one referenced as the worker default, a preset with model/reasoning/args, a bare preset); the error-JSON stderr format against Python's exact separators/escaping for an error path; graph config across all 4 harnesses plus --raw (against this installation's own pinned codegraph release) and the tool-unavailable error path; metadata snippet plain, --raw, and a --home path with spaces (exercises the shlex quoting); Store's registration-key hash checked against the real Python registration_key(); init and doctor end-to-end against the real Python reference live, in this dev checkout's own real --home (safe — both are read-only paths, the same observation each already performs on every ordinary invocation here), including matching exit codes; plus Go unit tests for worker-task-matching/linked-worktree detection (roleinit) and env-override/pinned-local/PATH precedence (toolpath).
  • Repository verification, run seven times as commits landed:
    • MISE_ENABLE_TOOLS=go,python,node python3 .agents/skills/verify/scripts/verify_run.py --base 69bc7ea07fb37e2725309e928465992ec5586890verify: pass at candidate 65a43e3913b4d706c21b4ab8ee2bbb53ff24f4c0 (77 scenarios pass, 9 not-run/manual; record .artifacts/verification/20260910T190020Z-fb926836/run.json).
      • A first run at that same candidate reported verify: fail with 77 scenarios failing. Diagnosis found exactly one underlying test failure: tests/test_returns.py::ReturnsTest::test_concurrent_question_report_and_refresh_keep_every_obligation_with_bounded_sends, a concurrency race (5 CLI subprocesses via ThreadPoolExecutor, then an assertion reads versions(task)['requested'] before the lock-protected write is guaranteed to have landed). Reproduced it failing 1/3 times in isolation with zero code changes in between on the same checkout — confirmed pre-existing and unrelated to this branch, which is 100% additive under go/ and touches nothing in tests/test_returns.py or the refresh/returns code path. Not fixed here; out of scope for Full Go/Cobra port of sumctl to replace the Python CLI (owner-directed, supersedes #38/#39 prototype scope) #94.
    • verify: pass at candidate bbbfa63c4bd67aac7586f9881921d0b153245a65 (the error-fix + preset commits), clean (record .artifacts/verification/20260910T192255Z-4d25b5aa/run.json).
    • verify: pass at candidate 2aecf5fcee541621b5e451f69032b1b14e7ba378 (the graph config commit), clean (record .artifacts/verification/20260910T201257Z-d77b2ead/run.json).
    • verify: pass at candidate c0f459cbd7bad853f6c132375acc11b5eeef0044 (the metadata snippet commit), clean (record .artifacts/verification/20260910T203430Z-31c9107b/run.json).
    • verify: pass at candidate f279121b3b9ba1ba8767608c867038b95fce437b (the Store registration foundation), clean (record .artifacts/verification/20260910T205615Z-2c37e544/run.json).
    • verify: pass at candidate 544fdea04b84492b3c98b97a1c37256cc0db5710 (ATTRIBUTIONS.md + the init port), clean (record .artifacts/verification/20260910T214119Z-828b6026/run.json).
    • verify: pass at candidate b437af26bb894a5fd0770db7bc59879be6b7aafc (the doctor port), clean (record .artifacts/verification/20260910T220630Z-d87480e5/run.json).

No manual/live Herdr scenario was run (this is a developer checkout, not a live installation).

Review status

Independent review was not performed beyond the automated verification above — no separate reviewer pane was launched for this developer-checkout PR. Boss decides merge.

Limitations / what's not in this slice

  • Only 8 of ~35 sumctl subcommands (release-contract, settings show, preset list, preset show, graph config, metadata snippet, init, doctor) are natively ported; everything else still runs through the Python reference.
  • init's designated/coordinator branch (live pane verification, claim/reclaim, contract state, pump, hook/metadata summaries) is not ported and stays on the Python reference — deliberately, given how safety-critical it is.
  • status, inbox, show, context and most other read-only commands are the natural next targets.
  • Default --home resolution for native commands is not implemented; every native path requires --home explicitly.

cs-test-runner added 11 commits September 10, 2026 17:30
First command of the full sumctl Python->Go port: read-only, no
subprocess/state dependencies, so it is a clean starting slice.
Differential test runs the real Python bin/sumctl release-contract
and asserts byte-identical stdout against the Go implementation.
…94)

Most remaining sumctl commands read/write task.json, state.json, and
settings via lib/sumctl.py's atomic_json/read_json/Store. Go's
encoding/json can't preserve Python dict insertion order on
round-trip, so add ordjson.Object (order-preserving JSON object) with
an encoder matching lib/sumctl.py's exact `json.dumps(value, indent=2,
ensure_ascii=True)` output (2-space indent, empty containers stay
inline, non-ASCII escaped to \uXXXX with surrogate pairs) and atomic
file writes matching atomic_json (temp file in the same dir, fsync,
rename, fsync directory).

store.Store ports Store's __init__/init/lock/path/read/save/all
methods (task ID validation, symlink refusal, flock-based exclusive
locking, sorted task listing). Differential-tested: ordjson.WriteFile
against the real Python atomic_json via a small reference script that
loads lib/sumctl.py with importlib, for a payload covering nesting,
arrays, and non-ASCII/control-character escaping.

Session/registration/designated-owner methods are not yet ported;
they aren't needed until doctor/status land.
Ports lib/sumctl.py's capacity_view/load_settings and the validators
it depends on (capacity, worker, presets, reviewer, launch-value
adapters) so `settings show` runs natively instead of shelling to
Python. Validation error text is byte-identical, including Python
repr()-formatted values in messages (unknown keys, invalid capacity,
launch-value/adapter mismatches, preset-reference lookups) via a new
pyrepr package.

Native handling only kicks in for exactly `settings show` with an
explicit --home; `settings set`/`preset`/anything else, or `settings
show` without --home, still falls through to the Python reference
unchanged (no attempt yet at replicating sum's default --home
resolution for native commands).

Differential-tested against the real Python bin/sumctl across seven
scenarios: empty store, populated settings, invalid capacity value,
unknown preset reference, unknown capacity key, a preset arg
conflicting with its own resolved model flag, and occupancy counting
across active/archived tasks.
Found while differential-testing preset show's error path: the
compiled entrypoint's top-level error handler used encoding/json's
compact defaults (no space after ':'/','), while lib/sumctl.py's
matching path is plain json.dumps({"error": str(exc)}) — the default
(non-indent) separators are (', ', ': '), and ensure_ascii is implied
throughout the module. A script comparing {"error": ...} byte-for-byte
would see `{"error":"x"}` from Go and `{"error": "x"}` from Python.

Adds ordjson.MarshalCompact for this shape (object/array/string/number
encoding matching MarshalIndent, just without newlines/indentation)
and switches the entrypoint to it. Updates the existing cancellation
test's expected substring and adds a differential test against the
real Python reference's stderr for an error path.
Ports lib/sumctl.py's preset_list/preset_show, reusing the settings
package's already-ported load_settings/validate_preset_reference:
preset_launch (the exact argv `dispatch --preset` would append,
including adapter-flag construction) and preset_references (worker/
reviewer default lookups).

Native handling only kicks in for exactly `preset list` or
`preset show NAME` with an explicit --home; `preset set`/`delete`,
anything else, or missing --home, still falls through to the Python
reference unchanged.

Differential-tested against the real Python bin/sumctl: list on an
empty store, list with multiple presets (one referenced as the worker
default), show a preset with model/reasoning/args, and show a bare
preset.
Ports lib/sumctl.py's graph_config/graph_tool (go/internal/graph):
locate the pinned codegraph binary (SUM_CODEGRAPH_BIN override, else
<runtime>/.local/bin/codegraph derived from the reference helper's own
path, matching lib/sumctl.py's RUNTIME resolution without needing the
full ROOT/SUM_INSTALL_ROOT machinery), probe its --version, and print
the per-harness MCP snippet (claude/cursor/opencode as JSON via
ordjson, codex as TOML) plus --raw's bare-snippet-only output.

`graph status`/`graph init` are unrelated (task-scoped, need a real
task record) and stay on the Python reference; `graph` only goes
native for the exact `config --harness NAME [--raw]` shape recognized
by parseGraphConfigArgs, falling back to compat for anything else
(including an invalid --harness, so Python's own argparse choice
validation/usage text stays exact).

Adds ordjson.QuoteString (exported single-value JSON-string encoding,
reused for the codex TOML snippet's command path) and reuses the
existing MarshalIndent path for the JSON-format snippets so their
formatting is guaranteed to match the outer emit() convention.

Differential-tested against the real Python bin/sumctl and the pinned
codegraph release already staged in this installation: all four
harnesses, --raw, and the tool-unavailable error path (via the
compiled binary, since that error surfaces through the top-level
stderr handler fixed in 46990f0).
Ports lib/sumctl.py's metadata_snippet: the static config.toml text a
user merges to render sum's sum_* sidebar tokens, plus the token/state
name lists and the exact enable/inbox commands to run next
(command_for(store, "metadata", ...)).

command_for shells out through shlex.join for reuse as a copy-pasted
shell command, so this adds go/internal/shquote (Quote/Join matching
Python's shlex.quote/join exactly: word/@%+=:,./- chars pass through
unquoted, anything else gets single-quoted with '"'"' escaping embedded
quotes) rather than hand-formatting paths that might contain spaces.

`metadata` only goes native for exactly `snippet` or `snippet --raw`
with an explicit --home; `enable`/`disable`/`status`/`sync`/`inbox`
and anything else still shell out to the Python reference unchanged.

Differential-tested against the real Python bin/sumctl: plain output,
--raw, and a --home path containing spaces (exercises the shlex
quoting in the emitted enable/inbox commands).
Completes the Store port (go/internal/store) with the session/
registration methods every registration-aware command needs:
Designated (state.json present, dev.json absent), Owner (context.json
if present), Registration/Register/Registrations (registration_key's
sha256-of-machine/session/pane hash, identity mismatch detection,
registered_at preserved across re-registration, the exact mcp/
sum_version/schema shape Register writes).

Not wired into any CLI command yet — doctor/status/init need context()
(Herdr pane env detection) ported first, which is a separate unit.
registrationKey verified against the real Python registration_key()
for a fixed input; the rest is covered by Go unit tests (no existing
CLI surface exposes these methods' output directly to differential-
test against yet).
One page crediting the projects that shaped sum, per the owner's
required credit list and editorial constraints: conceptual inspiration
vs. direct dependency vs. adapted code vs. historical lineage vs.
evaluated-not-installed integrations are kept distinct, and the four
must-find mappings (Firstmate -> agent distro, Oh My Pi -> builtins,
Solo -> meta-harness, Unpeel -> MCP pane/session management) are
called out explicitly at both ends of the page.

README's prior one-line "License and inspiration" blurb now points
here instead of duplicating a shorter version of the same credit.
CONTRIBUTING's existing attribution checklist item now names this file
directly.

Verified every referenced GitHub repo and file path resolves via
`gh api`, and fetched the plain product pages (Solo, Delta, Herdr,
mise) to confirm they're live; unpeel.com blocks scraping (403) but
resolves as a live domain, not a dead link.
Ports context()/session_from_env() (go/internal/store/context.go) and
init()'s non-designated path (go/internal/roleinit) — installation_hint
(is this checkout a linked worktree of a real sum installation?),
matching_task (are we a dispatched worker's own pane?), and
development_marker (.sum/dev.json), assembled into the exact
role/home/task/installation_home/note shape the Python reference
returns for a developer or dispatched-worker pane.

The designated/coordinator branch — live pane verification via Herdr,
coordinator claim/reclaim, contract state, cleanup_pending, pump,
hook/metadata summaries — is NOT ported. It is sum's most
safety-critical path (a bug could misfire a coordinator claim or lose
a registration) and is explicitly out of scope here; `init` only goes
native when the store is confirmed not designated, and falls back to
the Python reference for every other shape (unrecognized --role,
designated store, anything init's own argparse would need to reject).

Differential-tested against the real Python bin/sumctl init in this
very dev checkout (safe: the non-designated branch never writes —
no store.lock, no atomic_json — same read-only observation `init`
already performs here on every invocation this session), plus Go
unit tests for the worker-task-matching and linked-worktree-detection
logic using throwaway git fixtures.
Ports doctor(store) in full: tool() lookups for python3/node/git/gh/
herdr/quota-axi/lsof (go/internal/toolpath: SUM_<NAME>_BIN override,
then the pinned <runtime>/.local/bin/<name>, then PATH), herdr_version/
ensure_version and the herdr-context pane-get probe (go/internal/
herdrclient: a small JSON-RPC-style wrapper around the herdr binary
matching lib/sumctl.py's herdr()/run() error text for timeouts,
nonzero exit, and non-JSON output), the gh --attach probe, harness
detection over the same HARNESSES table, the Mesh-installed marker
file check, and the codegraph check via the already-ported graph.Tool.
Reuses Store.Designated/Owner/Registration for the role check.

doctor is fully read-only ("never binds") so it needed no scope
reduction like init did; every sub-probe here is observation only.
Exit code now follows value["ok"] (0 when every check passed, 1
otherwise) rather than the default always-0, matching the Python
dispatcher's special-cased handling of this one command.

Differential-tested end to end against the real Python bin/sumctl
doctor, live, in this dev checkout (byte-identical stdout AND matching
exit code) — safe, since every check here is read-only, the same
observation `doctor` already performs on every ordinary invocation.
Falls back to the Python reference for any unexpected argument (doctor
takes none). Added Go unit tests for toolpath's env-override/pinned-
local/PATH precedence.
@douglasjarquin
douglasjarquin merged commit 81dbdda into main Sep 11, 2026
1 of 2 checks passed
@douglasjarquin
douglasjarquin deleted the sum-dev/issue94-go-port branch September 11, 2026 02:07
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