Skip to content

feat(agent): complete middleware pipeline with config-driven ordering - #11

Merged
buchenberg merged 7 commits into
mainfrom
feature/middleware-pipeline
Jul 18, 2026
Merged

feat(agent): complete middleware pipeline with config-driven ordering#11
buchenberg merged 7 commits into
mainfrom
feature/middleware-pipeline

Conversation

@buchenberg

@buchenberg buchenberg commented Jul 18, 2026

Copy link
Copy Markdown
Owner

Summary

Completes the middleware pipeline plan (Phases 2-3), removes the legacy code path, and adds comprehensive documentation.

Phase 2 — Config-driven middleware

  • MiddlewareConfig with enabled/disabled lists in config.yaml
  • builtinMiddleware registry maps canonical names to constructors via MiddlewareBuilder func(l *Loop) Middleware
  • resolvedPipelineNames() applies config overrides to default set
  • Scaffold config updated in create.go

Phase 3 — New middleware (4 files)

Middleware File Hook What it does
PermissionMiddleware middleware_permission.go PostModel Path-glob allow/deny tool filtering
ToolConcurrencyMiddleware middleware_toolconcurrency.go Semaphore-capped goroutine pool via Loop.toolSem
SubAgentMiddleware middleware_subagent.go PostModel Task depth cap enforcement
PromptCachingMiddleware middleware_promptcaching.go PrepareStep Anthropic cache-control on system + tool msgs

Phase 3e — Compaction enhancements

  • CompactionMiddleware takes configurable threshold (default 0.8)
  • compactContext() signature changed to accept threshold float64
  • Preflight (PrepareStep) + post-iteration (PostTool) hooks

Cleanup

  • Removed legacy code path: runLegacy() (115 lines), executeToolsParallel() (97 lines), denyTool() (23 lines)
  • Removed AgentMode field from Loop — single unified middleware pipeline
  • Added CacheControl to types.Message (omitempty, no-op for non-Anthropic)

Docs

  • docs/architecture.md: full architecture reference covering agent loop, middleware pipeline, tool execution, streaming, compaction, hook events, provider interface
  • README.md: new Development section (build, test, lint, cross-compile, repo layout, architecture link), updated config docs
  • .kilo/agent-loop-comparison.md: implementation status for all 10 recommendations

Verification

gofmt -l .       → empty
go vet ./...     → clean
go test ./...    → all pass
yaah doctor      → OK
yaah "say hi"   → agent loop executes, streams response

- Create middleware interface and pipeline runner
- Extract existing behavior into 5 middleware: steer, followup, compaction, approval, loop detection
- Add AgentMode config gate (legacy/middleware)
- Fix message persistence bug in middleware mode (CompactionMiddleware)
- Add loop detection test for middleware mode
- Update docs with middleware pipeline plan

Entire-Checkpoint: 4d6891baff7d
…ugin

Remove .opencode/plugins/entire.ts which was previously tracked, and add
both .pi/ and .opencode/ directories to .gitignore to keep local agent
tooling artifacts out of the repo.

Entire-Checkpoint: 3983f6023812
…ipeline

- Move approval deny/ask logic from ApprovalMiddleware into executeAndCollect
- Fix ToolResult.Args to use actual args instead of result content
- Simplify CompactionMiddleware by removing unused provider fields
- Make LoopDetectionMiddleware.toolCallHash a package-level function
- Remove redundant compaction call from runMiddleware (handled by middleware)
- Add ~ path expansion via expandHomeDir to all file tools
- Include working directory in environment detection
- Suppress MCP startup stderr in agent sessions
- Remove stale planning docs

The middleware pipeline is now always-on; AgentMode == "middleware"
routes to the legacy code path.
- Display tool args and duration on the same line as the tool name
- Add formatDuration helper showing ms for <1s durations
- Add leading/trailing newlines before provider info and after streaming
- Show spinner newline before first token for cleaner output
Phase 2 — Config wiring:
- MiddlewareConfig with enabled/disabled lists in config.yaml
- builtinMiddleware registry maps names to constructors
- resolvedPipelineNames() applies config overrides

Phase 3 — New middleware:
- PermissionMiddleware: path-glob allow/deny tool filtering
- ToolConcurrencyMiddleware: semaphore-capped goroutine pool
- SubAgentMiddleware: task depth cap enforcement
- PromptCachingMiddleware: Anthropic cache-control breakpoints

Phase 3e — Compaction enhancements:
- CompactionMiddleware accepts configurable threshold
- compactContext now takes threshold float64 parameter

Cleanup:
- Remove legacy code path (runLegacy, executeToolsParallel, denyTool)
- Remove AgentMode field, unified single pipeline path
- Add CacheControl to types.Message (omitempty, no-op for non-Anthropic)

Docs:
- README: development section, config docs, remove "test commit"
- docs/architecture.md: full architecture documentation
- .kilo/agent-loop-comparison.md: status updates for all recommendations

Entire-Checkpoint: 6933fbdd5279
@buchenberg
buchenberg merged commit 329df82 into main Jul 18, 2026
8 checks passed
@buchenberg
buchenberg deleted the feature/middleware-pipeline branch July 19, 2026 07:04
buchenberg added a commit that referenced this pull request Aug 7, 2026
Task #11: Split newAgentSessionWithOptions into focused builders
- wiring_otel.go: initOtel + wrapProviderWithOtel (extracted inline provider wrapping)
- wiring_mcp.go: initMCP (moved to its own file)
- wiring_prompt.go: buildSystemPrompt + buildMainPrompt (extracted inline prompt layers,
  memory enrichment, guidelines, directive injection, and quick-ref assembly)
- wiring.go slimmed from 396 to ~210 lines; removed dead layers.Skills assignment
  (was set after prompts.Build, never read)

Task #12: Complete ContextManager extraction — eliminate state sync dance
- Added State *LoopState pointer to ContextManager; compaction methods now read/write
  mutable state (Messages, PreviousSummary, token counts, compaction tracking) directly
  through the pointer instead of copy-in/copy-out
- Removed 9 duplicate state fields from ContextManager (Messages, PreviousSummary,
  LastPromptTokens, LastCachedPromptTokens, IneffectiveCompactions, LastCompactionTokens,
  CompactionBudgetMultiplier, CompactionSavingsHistory, CompactionForcedByOverflow)
- Eliminated 19-line sync dance in Loop.compactContext and 3-line dance in Loop.trimContext
- Removed redundant CtxMgr.Messages assignments in loop.go, tools.go, turn.go
  (now no-ops since CtxMgr.State points to Loop.State)
- ctxMgr() lazily sets State = &l.State when nil (backward compatible with tests)
buchenberg added a commit that referenced this pull request Aug 7, 2026
#174)

* Split wiring.go builders and eliminate ContextManager state sync dance

Task #11: Split newAgentSessionWithOptions into focused builders
- wiring_otel.go: initOtel + wrapProviderWithOtel (extracted inline provider wrapping)
- wiring_mcp.go: initMCP (moved to its own file)
- wiring_prompt.go: buildSystemPrompt + buildMainPrompt (extracted inline prompt layers,
  memory enrichment, guidelines, directive injection, and quick-ref assembly)
- wiring.go slimmed from 396 to ~210 lines; removed dead layers.Skills assignment
  (was set after prompts.Build, never read)

Task #12: Complete ContextManager extraction — eliminate state sync dance
- Added State *LoopState pointer to ContextManager; compaction methods now read/write
  mutable state (Messages, PreviousSummary, token counts, compaction tracking) directly
  through the pointer instead of copy-in/copy-out
- Removed 9 duplicate state fields from ContextManager (Messages, PreviousSummary,
  LastPromptTokens, LastCachedPromptTokens, IneffectiveCompactions, LastCompactionTokens,
  CompactionBudgetMultiplier, CompactionSavingsHistory, CompactionForcedByOverflow)
- Eliminated 19-line sync dance in Loop.compactContext and 3-line dance in Loop.trimContext
- Removed redundant CtxMgr.Messages assignments in loop.go, tools.go, turn.go
  (now no-ops since CtxMgr.State points to Loop.State)
- ctxMgr() lazily sets State = &l.State when nil (backward compatible with tests)

* Address PR #174 review: OTel env flag, memoryGuidelines scope, budget multiplier init

- wiring_otel.go: evaluate YAAH_OTEL_ENABLED before the early return so
  the env flag can enable OTel when config disables it (pre-existing bug)
- wiring_prompt.go: move memoryGuidelines from package-level const into
  buildSystemPrompt function scope (no globals per AGENTS.md)
- lifecycle_init.go: initialize CompactionBudgetMultiplier=1.0 in ctxMgr()
  lazy path so Loop.Compact has a nonzero preservation budget before
  applyDefaults() runs

* docs: remove completed plans and implemented ADRs

Delete plan files for fully-implemented features (max iterations
dialog, TUI-MCP bridge, quiet mode, task pane separation) and the four
ADRs (engine-view separation, middleware pipeline, functional options,
event-driven architecture) whose content is now covered by
docs/architecture.md.

Update doc references across CONTRIBUTING.md, architecture.md, and
code-organization.md to point at architecture.md in place of the
retired ADRs, and fix stale file paths to reflect the recent runner
refactor (cmd/yaah/subagent_runner.go -> internal/agent/runner/).

* Delete implemented ADRs and plan docs, fix stale references

- Deleted 4 implemented ADRs (0001-0004: engine-view separation, middleware
  pipeline, functional options, event-driven architecture) — all Accepted
  and fully implemented; content covered by architecture.md
- Deleted 5 implemented plan docs from .agents/plans/ (tui-mcp-bridge #159,
  tui-quiet-mode #159, tui2-task-pane-separation #160, max-iterations-dialog,
  web-ui-commands #162) — all features shipped
- Updated docs/adr/README.md and CONTRIBUTING.md to remove dead ADR links
- Fixed stale references to cmd/yaah/subagent_runner.go in architecture.md,
  PROMPT-INJECTION.md, and code-organization.md (now internal/agent/runner)
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