Skip to content

WIP: fix: add ACPCommand/ACPArgs fields and protocolVersion to ACP init#1

Draft
tesdal wants to merge 94 commits into
mainfrom
fix/opencode-acp-command
Draft

WIP: fix: add ACPCommand/ACPArgs fields and protocolVersion to ACP init#1
tesdal wants to merge 94 commits into
mainfrom
fix/opencode-acp-command

Conversation

@tesdal
Copy link
Copy Markdown
Owner

@tesdal tesdal commented Apr 20, 2026

Summary

Two independent fixes for OpenCode ACP support in Gas City:

1. Include protocolVersion in ACP initialize handshake

The ACP spec requires protocolVersion in InitializeParams. Without it, strict servers (e.g. OpenCode) reject the handshake. Claude Code was lenient and masked the non-compliance.

2. Add ACPCommand/ACPArgs for transport-specific provider commands

Providers like OpenCode need different commands for ACP vs tmux transport (opencode acp for ACP, bare opencode for tmux). The existing Args field applies to both transports with no override. Adds ACPCommand and ACPArgs fields that fall back field-by-field to Command/Args.

Known limitation

Resume/reconciliation paths (session_runtime.go, cmd_session.go, worker_handle.go) pass transport="" to BuildProviderLaunchCommand. These paths use the stored command from session creation, which already contains the resolved ACP command — so they work correctly in practice. Threading transport through these paths would require adding a transport field to session.Info, which is a larger change.

Closes gastownhall#1008

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR extends provider configuration to support ACP-specific launch overrides (acp_command, acp_args) and updates the ACP JSON-RPC initialize handshake to include an explicit protocolVersion, with corresponding resolver, docs, and template-launch updates.

Changes:

  • Add ACPCommand / ACPArgs to provider specs and resolved provider, plus an ACPCommandString() helper.
  • Merge/copy the new fields through provider resolution and packing, and document them in schema/reference docs.
  • Include protocolVersion: 1 in ACP initialize request params.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
internal/worker/builtin/profiles.go Adds ACP command/args fields to built-in provider specs and clones ACP args.
internal/runtime/acp/protocol.go Adds protocolVersion to ACP initialize params and sets it to 1.
internal/config/provider.go Adds new config fields and introduces ACPCommandString().
internal/config/resolve.go Merges ACPCommand and ACPArgs through provider resolution and folding.
internal/config/pack.go Deep-copies ACPArgs in provider spec packing.
cmd/gc/template_resolve.go Uses ACP-specific command string when building the template command for ACP sessions.
internal/config/resolve_test.go Extends merge field-sync test data to include the new fields.
internal/config/field_sync_test.go Marks new ACP fields as intentionally not patchable.
docs/schema/city-schema.txt Documents new acp_command / acp_args fields in schema.
docs/schema/city-schema.json Documents new acp_command / acp_args fields in schema.
docs/reference/config.md Documents new acp_command / acp_args provider fields.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread internal/config/resolve.go Outdated
rp.PrintArgs = make([]string, len(spec.PrintArgs))
copy(rp.PrintArgs, spec.PrintArgs)
}
if len(spec.ACPArgs) > 0 {
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

specToResolved only deep-copies ACPArgs when len(spec.ACPArgs) > 0, which collapses an explicitly configured empty slice (e.g., acp_args = []) back to nil. That breaks the documented semantics where nil means “fall back to Args” but an empty slice should mean “override to no args”. Copy ACPArgs when spec.ACPArgs != nil (even if empty) to preserve this distinction.

Suggested change
if len(spec.ACPArgs) > 0 {
if spec.ACPArgs != nil {

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — changed to spec.ACPArgs != nil in 58a7341.

Comment on lines +150 to +155
var command string
if cfgAgent.Session == "acp" {
command = resolved.ACPCommandString()
} else {
command = resolved.CommandString()
}
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ACP command selection is based only on cfgAgent.Session == "acp". If the city-level session provider is "acp" (cfg.Session.Provider) and the agent leaves Session empty, this will incorrectly use CommandString() instead of ACPCommandString(), and the SupportsACP validation above will also be skipped. Derive an effective isACP value using both agent override and the city session provider (p.sessionProvider) and use it consistently.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ACP transport is agent-level only (Agent.Session = "acp"). There is no city-level ACP override — cfg.Session is the SessionConfig struct (tmux backend config), not a transport selector. An agent that leaves session empty always uses tmux, regardless of city config. No change needed.

Comment on lines +213 to +218
// ACPCommandString returns the command line for ACP transport sessions.
// Falls back to CommandString() when no ACP-specific command is set.
func (rp *ResolvedProvider) ACPCommandString() string {
cmd := rp.ACPCommand
args := rp.ACPArgs
if cmd == "" {
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ACPCommandString() is added, but it appears to be used only in cmd/gc/template_resolve.go. Other launch paths (e.g., internal/config/launch_command.go BuildProviderLaunchCommand) still hardcode resolved.CommandString(), so ACP sessions created via the API/worker may ignore acp_command/acp_args. Consider threading “is ACP transport” into those launch-command builders so ACP overrides are applied consistently.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — BuildProviderLaunchCommand now takes a transport parameter and uses ACPCommandString() when transport == "acp". All callers updated in 58a7341.

Comment on lines +213 to +228
// ACPCommandString returns the command line for ACP transport sessions.
// Falls back to CommandString() when no ACP-specific command is set.
func (rp *ResolvedProvider) ACPCommandString() string {
cmd := rp.ACPCommand
args := rp.ACPArgs
if cmd == "" {
cmd = rp.Command
}
if args == nil {
args = rp.Args
}
if len(args) == 0 {
return cmd
}
return cmd + " " + shellquote.Join(args)
}
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New ACPCommandString() behavior isn’t covered by tests yet (provider_test.go currently only asserts CommandString()). Add unit tests that cover: (1) ACPCommand set + ACPArgs nil fallback to Args, (2) ACPArgs explicitly empty overriding Args to none, and (3) ACPCommand empty falling back to Command.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — added TestACPCommandString with 6 subtests (full override, fallback, partial command-only, partial args-only, empty args, fallback-matches-CommandString) and TestBuildProviderLaunchCommandUsesACPCommand with 2 subtests in 58a7341.

Comment on lines 124 to 129
// newInitializeRequest creates an "initialize" request.
func newInitializeRequest() (JSONRPCMessage, int64) {
return newRequest("initialize", InitializeParams{
ClientInfo: ClientInfo{Name: "gc", Version: "1.0"},
ProtocolVersion: 1,
ClientInfo: ClientInfo{Name: "gc", Version: "1.0"},
})
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ProtocolVersion is now part of the initialize params and is set in newInitializeRequest(), but protocol_test.go’s round-trip test doesn’t assert that protocolVersion is present/expected. Add an assertion to prevent regressions (and update any fake ACP server fixtures if they start validating initialize params).

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — added TestInitializeRequest_IncludesProtocolVersion that asserts both struct field (params.ProtocolVersion == 1) and raw JSON presence in 58a7341.

Comment on lines +253 to +255
if city.ACPCommand != "" {
result.ACPCommand = city.ACPCommand
}
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Provider provenance tracking doesn’t appear to include the new acp_command/acp_args fields yet. buildProviderProvenance → recordScalarProvenance in internal/config/chain.go currently records title_model and print_args but not ACPCommand/ACPArgs, so gc config explain --provider ... (and any provenance-driven logic) won’t attribute these new fields correctly. Add both fields to recordScalarProvenance (and corresponding tests in provenance_test.go).

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — added acp_command and acp_args to recordScalarProvenance in 2d3ee8d.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 23 out of 23 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (3)

cmd/gc/worker_handle.go:336

  • resolvedWorkerRuntimeWithConfig rebuilds the command using resolved.CommandString() and calls BuildProviderLaunchCommand with transport == "". For sessions whose transport is ACP (e.g., agent.Session == "acp"), this can overwrite the stored ACP runtime command during SessionByID resolution and end up resuming with the wrong base command/args. Derive the effective transport for the session (from the agent template in cfg or from session bead metadata) and use ACPCommandString() / transport == "acp" when appropriate.
	command := strings.TrimSpace(info.Command)
	if !shouldPreserveStoredRuntimeCommand(command, resolved.CommandString()) {
		launchCommand, err := config.BuildProviderLaunchCommand(cityPath, resolved, nil, "")
		command = resolved.CommandString()
		if err == nil {
			command = launchCommand.Command
		}
	}

cmd/gc/cmd_session.go:412

  • resolvedSessionCommand always calls BuildProviderLaunchCommand with transport == "", even though gc session new can create ACP sessions when found.Session == "acp". That means acp_command/acp_args are ignored and the stored command may not match the actual transport. Thread the session transport into resolvedSessionCommand (or select ACPCommandString() when needed) and pass it through to BuildProviderLaunchCommand.
func resolvedSessionCommand(cityPath string, resolved *config.ResolvedProvider, optionOverrides map[string]string) (string, error) {
	if resolved == nil {
		return "", fmt.Errorf("resolved provider is nil")
	}
	launchCommand, err := config.BuildProviderLaunchCommand(cityPath, resolved, optionOverrides, "")
	if err != nil {
		return "", fmt.Errorf("resolving provider launch command: %w", err)
	}
	return launchCommand.Command, nil

internal/api/session_runtime.go:141

  • resolvedSessionRuntimeCommand always compares against resolved.CommandString() and builds a launch command with transport == "". For ACP sessions, this can incorrectly decide not to preserve the stored runtime command and regenerate a tmux command, ignoring acp_command/acp_args, which can break resume. Plumb the session transport into this function (e.g., from resolveSessionTemplate or bead metadata) and use ACPCommandString() / pass transport through to BuildProviderLaunchCommand when transport is "acp".
func (s *Server) resolvedSessionRuntimeCommand(resolved *config.ResolvedProvider, storedCommand string) (string, error) {
	if command := strings.TrimSpace(storedCommand); shouldPreserveStoredRuntimeCommand(command, resolved.CommandString()) {
		return command, nil
	}
	launchCommand, err := config.BuildProviderLaunchCommand(s.state.CityPath(), resolved, nil, "")
	if err != nil {
		return "", fmt.Errorf("building provider launch command: %w", err)
	}
	return firstNonEmptyString(launchCommand.Command, resolved.CommandString(), resolved.Name), nil

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread internal/config/pack.go Outdated
out.OptionDefaults = deepCopyStringMap(in.OptionDefaults)
out.OptionsSchema = deepCopyProviderOptions(in.OptionsSchema)
out.PrintArgs = append([]string(nil), in.PrintArgs...)
out.ACPArgs = append([]string(nil), in.ACPArgs...)
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

deepCopyProviderSpec uses append([]string(nil), in.ACPArgs...) which collapses a non-nil empty slice (configured acp_args = []) back to nil. That breaks the documented semantics where nil means “fall back to Args” but an explicit empty slice means “override to no args”. Preserve the nil vs empty distinction by copying ACPArgs only when in.ACPArgs != nil using a make(len) + copy (or equivalent).

Suggested change
out.ACPArgs = append([]string(nil), in.ACPArgs...)
if in.ACPArgs != nil {
out.ACPArgs = make([]string, len(in.ACPArgs))
copy(out.ACPArgs, in.ACPArgs)
}

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 20f7edc. Changed to make+copy guarded by in.ACPArgs != nil.

Comment thread internal/config/resolve.go Outdated
out.ACPCommand = r.ACPCommand
}
if r.ACPArgs != nil {
out.ACPArgs = append([]string(nil), r.ACPArgs...)
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

resolvedChainToSpec copies ACPArgs with append([]string(nil), r.ACPArgs...), which turns an explicitly configured empty-but-non-nil slice into nil. That loses the “empty slice overrides to no args” behavior for acp_args = []. Copy into a make([]string, len(r.ACPArgs)) buffer when r.ACPArgs != nil to preserve empty vs nil.

Suggested change
out.ACPArgs = append([]string(nil), r.ACPArgs...)
out.ACPArgs = make([]string, len(r.ACPArgs))
copy(out.ACPArgs, r.ACPArgs)

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 20f7edc. Changed to make+copy guarded by r.ACPArgs != nil.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 23 out of 23 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (2)

internal/api/session_runtime.go:141

  • Resume/reconciliation logic preserves a stored runtime command only if it matches (or has a prefix of) resolved.CommandString(). For ACP sessions where ACPCommand differs from Command (the new supported use case), storedCommand will typically match resolved.ACPCommandString() instead, so this check will fail and the code will rebuild a tmux command (transport "") and overwrite the ACP command on resume. Preserve the stored command when it matches either CommandString() or ACPCommandString() (or fetch the persisted transport from session metadata and compare/build using that transport).
func (s *Server) resolvedSessionRuntimeCommand(resolved *config.ResolvedProvider, storedCommand string) (string, error) {
	if command := strings.TrimSpace(storedCommand); shouldPreserveStoredRuntimeCommand(command, resolved.CommandString()) {
		return command, nil
	}
	launchCommand, err := config.BuildProviderLaunchCommand(s.state.CityPath(), resolved, nil, "")
	if err != nil {
		return "", fmt.Errorf("building provider launch command: %w", err)
	}
	return firstNonEmptyString(launchCommand.Command, resolved.CommandString(), resolved.Name), nil

cmd/gc/worker_handle.go:335

  • This resume path decides whether to keep info.Command by comparing it only against resolved.CommandString(). If the original session was ACP with ACPCommand != Command, info.Command will match resolved.ACPCommandString() and won’t be preserved, causing this code to rebuild a non-ACP launch command (transport "") and potentially break resume. Consider preserving when the stored command matches either CommandString() or ACPCommandString(), or thread/persist transport into session.Info so you can rebuild consistently.
	command := strings.TrimSpace(info.Command)
	if !shouldPreserveStoredRuntimeCommand(command, resolved.CommandString()) {
		launchCommand, err := config.BuildProviderLaunchCommand(cityPath, resolved, nil, "")
		command = resolved.CommandString()
		if err == nil {
			command = launchCommand.Command
		}

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@julianknutsen julianknutsen force-pushed the fix/opencode-acp-command branch 5 times, most recently from c9436f5 to eef02e6 Compare April 22, 2026 00:22
Jim Wordelman and others added 14 commits April 22, 2026 14:05
…all#799)

Pool-agent sessions resumed through the control-dispatcher path dropped
--dangerously-skip-permissions, --settings, and other schema-default
launch flags. The previous shouldPreserveStoredRuntimeCommand treated a
stored command that exactly equalled the bare provider binary ("claude")
as complete and preserved it, skipping BuildProviderLaunchCommand. The
resulting claude --resume <uuid> ran without the unrestricted
permission mode flag and wedged on interactive tool-call prompts.

Rebuild when the stored command is only the bare binary while still
preserving longer stored commands that already carry flags (so existing
paths that persist fully-resolved command strings continue to win). The
API path (internal/api/session_runtime.go) and the CLI worker-boundary
path (cmd/gc/worker_handle.go) carry parallel copies of the helper —
update both and add regression tests covering each.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pool-managed and ephemeral cursor-agent sessions were timing out during
create because the cursor profile had no ReadyPromptPrefix or
ReadyDelayMs, so the reconciler could not detect when the TUI was ready
to accept piped-in prompts. Named sessions worked because their prompt
is delivered as an exec arg, bypassing readiness detection.

cursor-agent 2026.04+ renders its composer input with a U+2192 (right
arrow) prefix after roughly 5-10s of startup (workspace-trust gate +
statsig + model load). Match the pattern used by the claude/codex/
gemini/copilot profiles:

  ReadyPromptPrefix: "\u2192 "
  ReadyDelayMs:      10000

Verified on macOS arm64 with cursor-agent 2026.04.17: ephemeral cursor
workers now claim beads, execute mol-do-work, write the requested files,
and run gc runtime drain-ack cleanly instead of cycling
stopped -> creating -> stopped against a deadline_exceeded outcome.

Made-with: Cursor
A bead-ID-shaped argument that doesn't resolve in the local store would
previously flow through `gc sling` unchecked — the existing
`beadExistsInStore` lookup fires only when the arg *doesn't* look like
a bead ID (the inline-text auto-create path). When the arg DOES look
like a bead ID, sling assumed it was a reference and dispatched.

That assumption is fine for humans who copy-paste IDs, but breaks down
in two cases:

1. LLM-driven dispatch that emits well-formed-looking IDs it invented
   (hallucinated IDs, unsubstituted template placeholders). Workers
   receive assignments to beads that never existed.
2. Plain human typos. `sc-qq3q0` for `sc-qq3a0` gets the same silent
   success — dispatch proceeds to a dead reference and workers stall.

## Historical context

`looksLikeBeadID` was added in 777c9ed (Phase 3a: name-based session
addressing) purely as a routing heuristic: decide whether a string is a
bead ID or a session-template name. It was never validation —
`resolveSessionID`, its original caller, errors out when the downstream
lookup fails.

efa9f33 (inline sling) reused `looksLikeBeadID` to disambiguate
inline text from bead IDs. The resulting condition `!looksLikeBeadID
&& !beadExistsInStore` only fires the store lookup for text-shaped
args; bead-shaped args went unchecked. This PR closes that gap.

## What changes

Add an explicit guard after the inline-create block: if the argument
matches `looksLikeBeadID` shape AND does not resolve in the store AND
`--force` is not set, sling errors with a clear message pointing at
`--force` as the escape hatch.

- Formula mode (`--formula` / `-f`) is exempt, since the argument is a
  formula name, not a bead ID.
- `--force` bypasses the check, consistent with the flag's existing
  role as the escape hatch for cross-rig routing and suspended-agent
  sling. Semantically: "I know the bead exists in a view the local
  store hasn't synced yet — dispatch anyway."
- Error message names the store root so operators can tell *which*
  store view disagrees with the caller's assumption.

Updated `--force` help text to reflect the new behavior.

## Tests

- `TestCmdSlingRefusesMissingBead` — bead-shaped arg with no matching
  bead → exit non-zero with a "not found" message that mentions
  `--force`.
- `TestCmdSlingForceBypassesMissingBeadCheck` — same setup with
  `--force` → the guard message does not appear in stderr.
- `TestCmdSlingAcceptsExistingBead` — seeded real bead + matching
  prefix → guard does not fire.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…-exists

fix(sling): refuse dispatch against bead IDs not in the store
Fix supervisor unregister naming, transient event provider lifetimes,
bead cache consistency, authoritative bead create responses, and
review follow-up docs/tooling regressions.
@julianknutsen julianknutsen force-pushed the fix/opencode-acp-command branch from eb9ecff to 0d2e185 Compare April 26, 2026 21:17
tesdal pushed a commit that referenced this pull request May 7, 2026
…gastownhall#1203)

## Summary

The session-first migration (`dd90ac0a`, Mar 8 2026) deleted the Agent
Protocol primitive (`internal/agent/agent.go` and the `Agent`/`Handle`
interfaces) and moved its responsibilities into `internal/session/`
(lifecycle) and `internal/runtime/` (providers). Commit `be8debd8` then
renamed `agent.*` events to `session.*`. The primitive list in AGENTS.md
and the architecture docs in `engdocs/architecture/` continued to
describe the deleted abstraction — for ~50 days.

This PR aligns the docs with HEAD. **No code changes.**

## Changes

- **Renamed** `engdocs/architecture/agent-protocol.md` → `session.md`
(88% similarity preserved by `git mv`). Title and summary rewritten to
frame as primitive #1 (Session) with a History note pointing at
`dd90ac0a`.
- **AGENTS.md**:
- Renamed primitive #1 from "Agent Protocol" to "Session"; reference
`agent.SessionNameFor` (in the `agent` helper package, not `session`)
- Fixed Messaging derivation: `Session.Nudge()` (delegating to
`runtime.Provider.Nudge()`) — `SendPrompt()` doesn't exist
  - Fixed Health Patrol derivation language
  - Updated progressive capability table (Level 0-1: Agent → Session)
- Replaced `agent` with `worker` in the canonical-domain list; footnoted
`internal/agent/` as a small helper package
  - Added new **"Active migrations"** subsection documenting:
- Worker boundary migration (started `12a0a848`, in progress) — names
the CI test `TestGCNonTestFilesStayOnWorkerBoundary` that enforces
non-test `cmd/gc/` files route through `worker.Handle`
    - Session-first migration (completed `dd90ac0a`)
- **`engdocs/architecture/`**: updated `nine-concepts.md`, `index.md`,
`glossary.md`, `messaging.md`, `dispatch.md`, `controller.md`,
`prompt-templates.md` — Agent Protocol references replaced with Session,
broken links fixed.
- **`event-bus.md`**: rewrote the event-type constants table from
`Agent*`/`agent.*` to `Session*`/`session.*` (matching
`internal/events/events.go:20-38` exactly, including the new
`SessionUpdated` constant the old table was missing). Renamed
`AutomationFired`/`Completed`/`Failed` to
`OrderFired`/`Completed`/`Failed`. Updated the storage-format example.
- **`health-patrol.md`**: fixed six event names renamed by `be8debd8`
(`agent.*` → `session.*`); replaced obsolete description of the removed
`internal/agent.Agent` interface with current helper-package
responsibilities; disambiguated the Erlang/OTP table's "Worker" row from
gascity's `internal/worker/` package.
- **`test/integration/E2E-PROVIDER-GAPS.md`**: three references to
`agent.started` → `session.woke`.
-
**`examples/gastown/packs/gastown/formulas/mol-digest-generate.toml`**:
runnable formula's `gc events --type=...` filter updated from broken
`agent.*` event names to current `session.*` names.
- Refreshed "Last verified against code" stamps on all touched docs to
2026-04-25.

## Why

The spec maintenance rule (originally in `specs/architecture.md`, now
distributed across the per-doc "Last verified" stamps) requires updating
docs in the same commit as referenced symbol renames. That discipline
wasn't honored on `dd90ac0a` or `be8debd8`, leaving the doc set
describing a deleted primitive. A new contributor reading AGENTS.md was
being directed to look for an abstraction that doesn't exist.

## Test plan

- [x] `make build` clean
- [x] `make check` clean (full `./...` suite green)
- [x] `make check-docs` clean
- [x] Repo-wide grep for `agent-protocol.md` returns 0 hits (no broken
cross-doc links)
- [x] All `Agent Protocol` references that remain are in intentional
History/migration footnotes (4 files)
- [x] All `session.*` event constants in docs match
`internal/events/events.go:20-38` exactly
- [x] `agent.*` event references remain only in `engdocs/archive/` (3
files, deliberately preserved historical analysis)

## Pre-flight review

Multi-agent review pipeline (3 Anthropic reviewers + Gas City
contributor checker) iterated 2 rounds before convergence. Iteration 1
caught a blocker (the worker-boundary claim was factually wrong) plus 5
majors; iteration 2 verified all fixes and approved with zero remaining
findings. Codex and Copilot CLI were unresponsive in this session and
skipped per the review skill's documented fallback policy.

---------

Co-authored-by: sjarmak <sjarmak@users.noreply.github.com>
Co-authored-by: Julian Knutsen <julianknutsen@users.noreply.github.com>
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.

fix: OpenCode provider should use 'opencode acp' as command

7 participants