Skip to content

feat(plugins): backend-engine-agentic-synthesizer plugin (Phase 6 #53)#288

Merged
charlie83Gs merged 3 commits intomainfrom
feat/backend-engine-agentic-synthesizer-plugin
Apr 21, 2026
Merged

feat(plugins): backend-engine-agentic-synthesizer plugin (Phase 6 #53)#288
charlie83Gs merged 3 commits intomainfrom
feat/backend-engine-agentic-synthesizer-plugin

Conversation

@charlie83Gs
Copy link
Copy Markdown
Contributor

Summary

What changes

Test plan

  • uv run --project plugins/backend-engine-agentic-synthesizer pytest -x -v (9 contract tests green — plugin identity, is_enabled, both (task_name, provider_id) contributions, factory round-trip, registry dispatch, task_name-mismatch rejection, missing-agent_context rejection, happy-path delegation for both tasks via sys.modules injection)
  • uv run --project libs/kt-config pytest -x -v (236 green)
  • uv run --frozen ruff format --check . / ruff check .
  • Pre-push hook ran full unit test suite — all green

🤖 Generated with Claude Code

Ships `plugins/backend-engine-agentic-synthesizer/` — contributes
the `langgraph-default` `AgenticTaskProvider` for both the
`synthesizer` and `super_synthesizer` tasks declared in
`GraphTypeComposition.agentic_tasks` on the default graph type.

Identity is `(task_name, provider_id)` — the registry narrows by
`task_name` first, so a single `langgraph-default` id covers both
tasks via two `AgenticTaskContribution` entries. Matches the shape
`DefaultGraphTypePlugin.composition().agentic_tasks` declares.

Per-call delegation pattern:
1. Dispatcher (synthesis worker) packs `agent_context`, `state`,
   and optional `recursion_limit` into `AgenticTaskContext.options`.
2. Provider constructs the relevant `SynthesizerAgent` /
   `SuperSynthesizerAgent` with the supplied AgentContext + model
   override, compiles the LangGraph graph, invokes `ainvoke`.
3. Returns the raw agent output — downstream Hatchet tasks narrow
   at their typed boundary.

Lazy-imports `kt_worker_synthesis` inside `run()` so the plugin's
`pyproject.toml` doesn't reverse the workers-depend-on-plugins
direction. Contract: the synthesis worker must co-install this plugin
for registry dispatch to work; if a worker loads the plugin without
the agent package, `run` raises `ImportError` — fail-fast signal,
not silent degradation.

Fail-fast paths:
- `task_name` mismatch (registry misrouting) → `ValueError`
- Dispatcher forgot to pack `agent_context` → `RuntimeError`

Registered in `kt_config.plugin.load_default_plugins`. **No worker
wiring this PR** — `synthesizer_wf` + `super_synthesizer_wf`
continue to construct agents directly until a follow-up threads
`plugin_registry.get_agentic_task_provider` through the dispatchers
(mirrors the definition / sync / source-cache / source-contribution
plugin-then-wire sequencing).

Tests: 9 contract tests — plugin identity, `is_enabled` default,
both `(task_name, provider_id)` contributions, factory round-trip,
registry dispatch, task_name-mismatch rejection, missing-agent_context
rejection, happy-path delegation (LangGraph compile + ainvoke) for
both synthesizer and super-synthesizer.
CI Backend Lint + every unit+integration job failed with:
  error: The lockfile at `uv.lock` needs to be updated, but
  `--frozen` was provided: Missing workspace member
  `kt-plugin-be-agentic-synthesizer`.

The plugin package was added to plugins/ but its workspace entry
didn't make it into uv.lock in the initial commit — the lock
update was dropped when reverting the release-bot version bump.
Re-run `uv lock` so every uv.sync (CI runs --frozen) picks up
the new workspace member.
#1 Raise default recursion_limit 100 → 500. Legacy workflows compute
   max(explore_budget * 30, 500); the old default silently truncated
   normal runs partway through if the dispatcher forgot to pack the
   knob. 500 matches the legacy floor so a regressed packing path
   stays viable; dispatchers still SHOULD pass their own computed
   limit. Module-level _DEFAULT_RECURSION_LIMIT constant names the
   floor.

#2 Drop the `self._gateway` dead field. Agents pick up the gateway
   off the dispatcher-supplied AgentContext at run time, so stashing
   the factory argument was cargo-cult and hid the real dependency
   edge. __init__ now consumes+discards the gateway to match the
   AgenticTaskContribution factory shape.

#3 State coercion caveat documented in module docstring. Workflows
   pass Pydantic SynthesizerState today; the wiring-PR dispatcher
   MUST pass either a model_dump() or a raw model instance so the
   agent's validators still run. dict(options.get('state')) narrows
   a Pydantic instance silently — flagged at the boundary.

#4 Extend _ctx test helper with model_id_override kwarg. Removes
   the rebuild-the-context dance in the synthesizer delegation test
   (previously had to reconstruct AgenticTaskContext just to set the
   override).

#5 Factor shared plumbing into _LanggraphAgentProviderBase. Subclass
   diff is three classvars (_task_name, _module_path, _agent_attr);
   run() + constructor + task_name/provider_id properties live on
   the base. A third task keyed by the same provider id lands as
   another one-liner subclass.

Tests: 9 contract tests green. Recursion-limit assertion updated
to 500 with a doc comment explaining the floor.
@charlie83Gs
Copy link
Copy Markdown
Contributor Author

Applied review feedback in commit dfd174a:

#1 recursion_limit 100 → 500. Module-level _DEFAULT_RECURSION_LIMIT constant matches the legacy max(explore_budget * 30, 500) floor so a dispatcher that regresses its packing path stays viable; dispatchers should still pass their own computed limit.

#2 Dropped self._gateway dead field. __init__ consumes+discards the factory argument (del gateway) — agents read the gateway off the dispatcher-supplied AgentContext at run time, so stashing it was cargo-cult.

#3 State coercion caveat documented in module docstring. dict(options.get('state')) still narrows Pydantic models silently — flagged as a wiring-PR contract: dispatchers MUST pass model_dump() or a raw model instance so validators still run.

#4 _ctx helper extended with model_id_override kwarg. Removed the rebuild-the-context dance in test_synthesizer_run_delegates_to_lazy_agent.

#5 Factored common base. _LanggraphAgentProviderBase owns __init__, task_name / provider_id props, and run() dispatch. Subclasses differ by three classvars (_task_name, _module_path, _agent_attr). Third task keyed by langgraph-default lands as another one-liner subclass.

Tests: 9 contract tests green; default-recursion-limit assertion updated from 100 → 500 with a doc comment explaining the floor. Full pre-push suite green.

@charlie83Gs charlie83Gs merged commit 9708d0f into main Apr 21, 2026
30 checks passed
@charlie83Gs charlie83Gs deleted the feat/backend-engine-agentic-synthesizer-plugin branch April 21, 2026 20:27
@github-actions
Copy link
Copy Markdown


Thank you for your submission, we really appreciate it. Like many open-source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution. You can sign the CLA by just posting a Pull Request Comment same as the below format.


I have read the CLA Document and I hereby sign the CLA


You can retrigger this bot by commenting recheck in this Pull Request. Posted by the CLA Assistant Lite bot.

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