Skip to content

WIP: migrate to protocol stage for better protocol handling. - #1501

Draft
FFengIll wants to merge 8 commits into
mainfrom
feat/protocol-stage-hardening-port
Draft

WIP: migrate to protocol stage for better protocol handling.#1501
FFengIll wants to merge 8 commits into
mainfrom
feat/protocol-stage-hardening-port

Conversation

@FFengIll

@FFengIll FFengIll commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

No description provided.

@FFengIll
FFengIll marked this pull request as draft August 5, 2026 05:23
…x design notes

Front-load the design and plan documents for the Protocol Stage feature port
(from codex/protocol-stage-hardening) as the first commit of the branch, so
every subsequent code commit is traceable to a written plan:

- .design/protocol-stage-chain.md: composable protocol stage chain rationale
- .design/protocol-stage-tool-loop.md: stage tool-loop semantics (max rounds,
  side-effect commit, failover replay safety)
- .design/protocol-recording-redesign.md: recording boundary redesign
- .design/harness-matrix.md: cross-stage test matrix layout
- docs/guardrails.md, README.md, cli/harness/README.md: user-facing notes

No code changes; documentation only.
…g from hardening

Port the self-contained stage core from codex/protocol-stage-hardening (off #1491):
- internal/protocol/stage: bridge/compose/registry/topology + anthropic/openai/responses bridges, guardrail, toolloop
- internal/protocol/{assembler,nonstream,stream,transform,wire,request}: protocol-layer additions and adjustments the stage depends on
- internal/record: new request recording lifecycle (recorder, provider_endpoint, boundary matrix)
- internal/obs: record/sink/slim additions + request_record test
- internal/guardrails/mutate: RewriteAnthropicToolUseEventDecision used by stage guardrail

All dependencies (protocol/*, guardrails, record, obs) land in this commit so the
unit builds clean: go vet ./internal/protocol/... ./internal/guardrails/... ./internal/record/... ./internal/obs/

Wiring into the server (protocol_stage_*.go glue, forwarding path fix, server skeleton
3-way merge) and cross-stage test matrix follow in subsequent commits.

Batch 1+2 of the protocol-stage-hardening port.
…o new package layout

Port the server-side stage integration from codex/protocol-stage-hardening, adapting to
main's post-#1493..#1495 package extraction (gateway core now lives in protocolserver,
MCP engine in mcpserver):

- protocol_stage_*.go glue (pipeline/selector/recording/tool_loop/anthropic/openai endpoints):
  moved into package protocolserver (was package server on hardening), since ProtocolHandler
  now lives there. Rewired imports: server/{forwarding,recording} -> protocolserver/*;
  server/transform folded into protocolserver/transform; mcpmodule alias -> internal/mcpserver.
- 16 files hardening modified at old internal/server paths (anthropic_message, openai_chat,
  openai_responses, protocol_{dispatch,handler}, failover_dispatch, guardrails_runtime_ai,
  mcp_hooks, module/mcp/{continuation_store,format_adapter,generic_loop_processor,
  generic_stream_interceptor,tool_executor}, servertool/executor): applied via clean 3-way
  merge (base=#1491) against their new protocolserver/mcpserver homes — 0 conflicts.
- anthropic_beta_stage*.go + tool_executor/continuation_store tests: moved to package mcpserver
  (their referenced symbols Tool/NewAnthropicBetaAdapter live there now); package mcp -> mcpserver.
- internal/mcp/runtime: ListServerToolsForAnthropicBetaInjection added by hardening.
- Restored the dropped `internal/protocol` import in protocol_handler.go (lost in 3-way merge).

Builds clean: go vet ./internal/{server,protocolserver,mcpserver,mcp,protocol,record,obs,guardrails}/...

Server-skeleton wiring (server.go/lifecycle/options 3-way merge) follows next.

Batch 3 of the protocol-stage-hardening port.
Complete the server-side wiring for the ported Protocol Stage pipeline
(hardening's server.go/server_lifecycle.go/server_options.go changes, 3-way merged
against main's post-#1493..#1495 skeleton):

- server.go: pass ProtocolStageEnabled into protocolserver.ProtocolHandlerDeps;
  add protocolStageEnabled and servertoolProviders fields to the Server struct.
- server_options.go: add WithProtocolStage and WithServertoolProviders options
  (servertool import repointed to internal/protocolserver/servertool).
- server_lifecycle.go: add ForceFlushRecordings (auto-merged clean; used by the
  protocol harness so a run leaves a complete recording artifact before exit).

The two real conflicts (server.go handler construction, server_options.go import)
resolved in favor of main's protocolserver layout, retaining hardening's additive
ProtocolStageEnabled/servertoolProviders surface.

go build ./... is clean end-to-end (server + cli).

Batch 4 of the protocol-stage-hardening port.
…t MCP runtime after builtin registration

WithServertoolProviders stored providers on the Server but nothing ever read
them, so in-process server-owned tools never reached the MCP runtime's
virtual registry. Re-register them in registerAdviserFromConfig so they
survive pipeline rebuilds and config hot reload.

Also restore the integration branch's construction order: RegisterBuiltinTools
must run before NewRuntime. The port created the runtime first, so on a
first-run config (no persisted MCP runtime yet) NewRuntime saw a nil config
and returned nil, disabling the whole stage tool loop and leaving requests
to fall back to legacy. Registering builtins first seeds the MCP runtime
config and yields a usable runtime on first boot.
…ate stash on session

Problem 1: a pure-managed round followed by a pure-external round returned
the external round with no continuation, so the server-tool results from the
earlier round never reached the model's final answer on the client's next
turn. Track internal rounds accumulated during the loop and stash them ahead
of the external assistant message; generalize the merge so a trailing
assistant segment receives the client's results as a new user turn instead
of folding them into a managed-results message.

Problem 2: on IP-fallback / no-session requests the continuation store
silently no-oped, so a mixed round executed the server-owned tool, filtered
its block, and dropped the result with no error. Put now reports failure and
the store exposes CanStash; the stage checks it before executing owned tools
and returns ErrContinuationUnavailable instead of committing side effects it
cannot carry. An external-only round after internal rounds degrades to
delivering the round without a stash (guardrail blocking still works).
…fects after dispatch

Problem 3: a pure-managed tool call on the last allowed round failed with
ErrMaxRounds, discarding the tool calls, while a mixed round on the same
round succeeded. maxRounds now bounds server-tool executions: the last
budgeted round executes its tools and the model gets one extra provider
round to produce the final answer. A further server-tool request fails
closed. Applies to the Beta complete/stream stages and the OpenAI Chat
stage; unify both stage defaults behind DefaultMaxRounds=3 (matching the
legacy MCP loop) and set the production Beta loop explicitly.

Problem 4: the Chat tool loop marked side effects committed only on a
successful executor return. An executor that dispatches an irreversible
action and then loses its response left the attempt uncommitted, so
failover could replay the action on another provider. ToolResult gains a
Dispatched signal (matching the Beta stage's existing behavior); the Chat
stage commits when err == nil || result.Dispatched, keeping pre-dispatch
validation and policy failures uncommitted.
Port the integration-test surface and user-facing wiring for the Protocol Stage
feature from codex/protocol-stage-hardening:

- internal/protocoltest: bridge/mcp/guardrail/recording matrix helpers + tests
  (bridge_matrix, mcp_matrix, mcp_recording_*, protocol_stage_server_test,
  protocol_stage_tool_loop_test, record_artifacts, guardrails); testenv gains
  rootServer + ProtocolStage/servertool options + ForceFlushRecordings;
  failover/matrix/virtual_client tests updated. Repointed stale imports
  (server/servertool -> protocolserver/servertool; server/module/mcp -> mcpserver).
- internal/command: expose EnableProtocolStage + WithProtocolStage plumbing
  (options/server_options{,_test}.go, command/server.go).
- cli/harness + cli/tingly-box: matrix and main_test updates.
- gui/wails3/run.go: pass WithProtocolStage(opts.EnableProtocolStage) in all launch modes.

Design notes for this feature live in a dedicated front-loaded commit
(docs(protocol-stage)) at the head of the branch.

Full module builds clean: go build ./... and go vet ./internal/... ./cli/...
produces the tingly-box binary. (experiments/ is untracked working-tree scratch,
not part of this port.)

Batch 5 (final) of the protocol-stage-hardening port.
@FFengIll
FFengIll force-pushed the feat/protocol-stage-hardening-port branch from c0075fc to 3527eea Compare August 5, 2026 05:39
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