Skip to content

refactor(agent): finish single semantic execution path across transports #68

Description

@M09Ic

Context

PR #62 established AOP as the Agent semantic event model and introduced the execution path:

AgentRuntime -> Session -> Run / Command

That is the correct kernel direction, but the follow-up review found that several callers and adapters still own parallel lifecycle, routing, protocol, rendering, or compatibility mechanisms. This issue tracks their convergence on top of #62.

The objective is not to introduce another runtime wrapper. AgentRuntime already owns the runtime context, session registry, turn IDs, worker wait group, provider/tool resources, and shutdown. It must remain the single control-plane owner.

Architectural principles

  1. One state owner: AgentRuntime owns sessions, active runs, cancellation, and shutdown.
  2. One semantic lifecycle: every production Agent execution emits one Session lifecycle and one Turn lifecycle through the same implementation.
  3. One Agent event model: AOP is forwarded, persisted, replayed, and rendered without projection into a second Agent envelope.
  4. Explicit protocols: run, command, direct tool execution, transport operations, and product events are different contracts.
  5. Presentation stays at the surface: terminal capture, ANSI handling, and Markdown fencing are not runtime semantics.

Current problems

1. Runtime ownership is duplicated at the transports

  • AgentRuntime already has a session map and turn-ID registry, but stdio and WebAgent keep additional session maps.
  • stdio and the WebSocket connection each keep their own active-run cancellation maps and asynchronous wait logic.
  • WebSocket session creation currently uses connection context, although sessions are expected to survive reconnects.
  • WebSocket and stdio do not share the same policy for missing turn_id, missing cancel targets, protocol errors, or shutdown draining.
  • Connection-local ownership caused a startup/reconnect race around the WebAgent session map.

Convergence:

  • Extend AgentRuntime; do not add a ProtocolHost or another stateful owner.
  • Add runtime-level session lookup/run/command/cancel operations by ID.
  • Register active and queued Runs in AgentRuntime, with one cancellation and cleanup policy.
  • Bind Sessions to runtime lifetime and Runs to request lifetime.
  • Let stdio and WebSocket retain only framing, I/O, connection-specific RPCs, and AOP forwarding.
  • Share protocol decoding/response construction through an AgentRuntime control method or a stateless dispatcher around it.

2. Run semantics differ by entry path

  • WebAgent interprets /continue and /followup; stdio treats the same strings as ordinary prompt content.
  • RunInput already has Continue, but the wire RunPayload does not expose it.
  • WebSocket has required a caller-supplied turn_id, while Runtime can generate one.
  • TUI, stdio, and WebSocket therefore reach the same Agent loop through different input normalization.

Convergence:

  • Add typed continuation semantics to RunPayload and use them from Hub/TUI/stdio/WebSocket.
  • Generate missing turn IDs in Runtime and apply one rule for empty input, cancellation, and correlation.
  • Remove WebAgent-only /continue and /followup prompt rewriting; callers use the typed field.

3. Command and direct-tool RPC are conflated

  • CommandPayload is a nullable union of a slash/shell command and ToolCall.
  • Hub direct-tool dispatch converges on command.result, even though the operation is not a command.
  • Hub also contains an AOP tool.result task-convergence path that current direct-tool producers cannot reliably reach.
  • Agent, WebAgent, Hub, and TUI each flatten or reinterpret tool content, details, progress, and errors.

Convergence:

  • Keep slash/shell command execution as command/command.result.
  • Route direct-tool requests and results exclusively as AOP tool.call/tool.result, using TaskID == ToolCallID as the correlation rule.
  • Keep remote tool-only node invocation in the control plane; do not pretend it is an Agent turn.
  • Centralize tool.Result to typed AOP result conversion, including text/image parts, structured details, errors, and progress correlation.
  • Delete the nullable CommandPayload.ToolCall path and duplicate command.result convergence.

4. Agent Session/Turn lifecycle outside Runtime is deferred

Subagent sync/async/fork and scanner verifier/sniper currently do not share the Runtime Session/Turn owner. They must be migrated together so this change does not introduce a half-adopted lifecycle primitive. That work is explicitly out of scope for this PR and will be planned separately.

5. Runtime commands depend on TUI implementation

  • Session.Command constructs a TUI console, captures stdout/stderr, strips ANSI, and converts terminal output back into Agent message parts.
  • WebAgent additionally wraps multiline output in Markdown fences.
  • Adapter controls (/stop), run controls (/continue), runtime commands, UI-only commands, and shell commands are distinguished by string conventions.

Convergence:

  • Extract a surface-neutral command router and structured command result.
  • Separate adapter control, run control, runtime command, UI command, and shell execution at the command definition boundary.
  • Let TUI render terminal output and Web render Markdown; neither transformation belongs in Session.Command.
  • Keep command-result AOP emission in one semantic location when a command is intended to become an Agent-visible message.

6. Timeline and replay maintain a parallel Agent model

  • Timeline parsing defines a legacy AgentEvent envelope alongside AOP.
  • The legacy renderer recognizes old message_end/text-oriented events rather than canonical message parts.
  • Structured tool-result content and current AOP typed payloads are flattened inconsistently.
  • Web ChatEvent still mixes Agent semantic projection with product-domain events.

Convergence:

  • Parse and render aop.Event directly with the canonical typed decoders.
  • Support current message, structured tool result content, usage, status, and lifecycle events.
  • Keep scan progress, SCO, assets, loot, and similar data as product-plane events rather than Agent events.
  • Narrow or rename remaining Web ChatEvent to product-domain events.
  • Delete the legacy AgentEvent parser and intentionally stop accepting historical Agent timeline JSONL.

7. Transitional mechanisms need a keep/remove decision

  • Run.Events and its per-run replay buffer have no clear production consumer while the global AOP bus is authoritative.
  • Web multiline command fencing is presentation behavior embedded in Agent handling.
  • Compatibility helpers and duplicate output conversions remain after feat(aop): unify agent transport end to end #62.
  • Provider/context/retry work and scanner feature additions were mixed into the earlier large diff but are not required for semantic convergence.

Convergence:

  • Inventory every transitional helper and mark it keep, move, or delete before changing it.
  • Keep Run.Events only if a concrete streaming/replay consumer is documented and tested; otherwise remove the extra log.
  • Move presentation transforms to their owning UI boundary.
  • Keep Provider, retry, context-window, and unrelated scanner feature work outside these refactor PRs.

8. Tool packages are buried behind internal architecture

  • Tool implementations live under pkg/tools/**, which makes the extension boundary look coupled to the application internals.
  • There is no short, canonical example showing the minimum Tool contract, registration, and focused test.
  • Adding another plugin layer would obscure the existing four-method interface rather than improve extensibility.

Convergence:

  • Promote tool implementations to top-level tools/** packages and update imports atomically.
  • Document the minimum four-method implementation, registration point, and table-driven test in tools/README.md.
  • Keep extension compile-time and explicit; do not add a registry framework, dynamic loader, manifest, or compatibility alias.

Target boundaries

Local/TUI -------------------------┐
stdio framing --------------------+--> AgentRuntime control API
WebSocket framing ----------------┘        |
                                            +--> Session registry / actor / cancel
                                                      |
                                                      v
                                              agent.ExecutionSession
                                                      |
                                                      v
                                                  Agent loop
                                                      |
                                                      v
                                                Tool executor
                                                      |
                                                      v
                                                     AOP
                                                      |
                                    +-----------------+-----------------+
                                    v                 v                 v
                                  render          persist/replay     transport

Direct-tool AOP tool.call ---------------> Tool executor -> AOP tool.result
Product events --------------------------> scan/SCO/assets/loot consumers

The shared transport code may be a stateless dispatcher or methods on AgentRuntime; it must not introduce another session/run registry.

Delivery structure

This work is delivered as one PR with one reviewable commit per finding:

  1. Remove unused Run replay and context-free handoff helpers.
  2. Rename and narrow ChatEvent to DomainEvent.
  3. Make AgentRuntime the only Session/Run/cancel state owner and unify stdio/WebSocket control semantics.
  4. Route direct tool calls and results exclusively through AOP.
  5. Delete legacy timeline Agent envelopes and parse canonical aop.Event directly.
  6. Promote pkg/tools/** to top-level tools/** and document the minimum extension path.
  7. Remove TUI construction and intermediate command payloads from Runtime command execution.
  8. Remove obsolete compatibility names and mechanism documentation.

No commit introduces a dual-read, dual-write, deprecated alias, temporary state owner, or new plugin framework. The PR remains buildable at every commit boundary.

Intentional compatibility breaks:

  • Historical legacy AgentEvent timeline JSONL is no longer accepted.
  • Mixed-version direct-tool peers using CommandPayload.ToolCall/command.result are not supported.
  • Tool import paths move from pkg/tools/... to tools/....
  • Agent skills use the explicit /skill:<name> command form.

Verification strategy

  • Table-driven Runtime control tests for open/duplicate/close/run/queue/cancel/close-during-run.
  • A shared conformance suite that sends equivalent request sequences through stdio and WebSocket and compares normalized control responses and AOP.
  • Race tests for Runtime, stdio, WebAgent reconnect/disconnect, queued cancellation, and shutdown.
  • Lifecycle contract tests for local, stdio, and WebSocket execution. Subagent/scanner lifecycle coverage is deferred with that migration.
  • Golden/round-trip tests for direct tool text, image, structured details, progress, and error results.
  • Timeline fixtures for canonical typed AOP, including structured tool results and command presentation metadata.
  • Frontend protocol typecheck/build whenever wire payloads change.

Acceptance criteria

  • AgentRuntime is the only owner of Agent session and run control state.
  • No transport adapter owns Agent-specific lifecycle, cancellation, or prompt normalization.
  • The same Run request has identical semantics across TUI, stdio, and WebSocket paths.
  • Every in-scope local, stdio, and WebSocket Agent execution has exactly one canonical Session/Turn lifecycle owner.
  • Commands use command request/result contracts; direct tools use AOP tool.call/tool.result exclusively.
  • Tool result conversion exists in one place.
  • AOP is the only Agent semantic event model used by new production code.
  • UI formatting is not performed by Runtime or transport-neutral Agent code.
  • Every transitional mechanism is either justified by a tested consumer or removed.

Non-goals

  • Reworking provider implementations, retry policy, context-window calculation, or model behavior.
  • Adding scanner capabilities unrelated to routing/lifecycle convergence.
  • Changing subagent or scanner verifier/sniper Session/Turn lifecycle in this PR.
  • Additional wire compatibility breaks beyond the explicit migration notes above.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions