Skip to content

feat(plugin): make agent a distributable gRPC binary (P1-T1) - #22

Merged
intel352 merged 2 commits into
masterfrom
feat/agent-grpc-binary
Jun 29, 2026
Merged

intel352 merged 2 commits into
masterfrom
feat/agent-grpc-binary

Conversation

@intel352

Copy link
Copy Markdown
Contributor

Makes the agent plugin a distributable external gRPC binary (Task 1 of the locked "agent plugin gRPC binary" plan). Mirrors workflow-plugin-infra.

What

  • cmd/workflow-plugin-agent/main.go: sdk.Serve → sdk.ServePluginFull (forward-compat for future CLI/hooks; nil cli+hooks for now).
  • .goreleaser.yml: full cross-platform build (linux/darwin/windows × amd64/arm64), CGO_ENABLED=0, ldflag -X Version, archives bundle plugin.json + plugin.contracts.json + LICENSE sidecars, checksums.txt, release.github, before-hook rewrites plugin.json version+URLs at release time.
  • plugin.json: type: builtin → external; reconcile capabilities.stepTypes to runtime truth (2 → 4: add step.agent_execute, step.provider_test); add homepage/repository/downloads for the URL-rewrite hook. version stays 0.0.0 dev-sentinel (goreleaser injects the release tag).
  • plugin_contracts_test.go: drop the now-wrong assertion that forbade step.agent_execute/step.provider_test from plugin.json stepTypes — that encoded the drift; capabilities must list ALL runtime steps.
  • provider_manifest_test.go (new): asserts *AgentPlugin satisfies sdk.{PluginProvider,ModuleProvider,StepProvider,TypedModuleProvider,TypedStepProvider,ContractProvider}; Manifest() returns canonical name; and plugin.json stepTypes/moduleTypes == runtime (P6 drift gate).

Why no adapter file

The plan's verified fact said "adapter adds Manifest() only." But Manifest() is already on *AgentPlugin (plugin.go:123), and sdk.{StepProvider,ModuleProvider,TypedStepProvider,TypedModuleProvider} are already implemented in typed_contracts.go. So *AgentPlugin directly satisfies sdk.PluginProvider — no internal/grpc_adapter.go wrapper is needed. agent.New() is passed straight to ServePluginFull. This is the minimal correct shape.

Evidence (real output)

Build + test green:

GOWORK=off go build ./...   # EXIT 0
GOWORK=off go test ./...     # all packages ok (3 new tests pass)

P6 gate — wfctl plugin verify-capabilities (wfctl built from workflow origin/main e6ad6b7d6):

# ldflag-injected build (simulates release):
OK    workflow-plugin-agent 0.10.0 (plugin.json: 0.10.0)   # EXIT 0

Runtime GetManifest matches plugin.json exactly: 4 steps (step.agent_execute, step.provider_test, step.provider_models, step.model_pull) + agent.provider module.

Goreleaser dry-run (goreleaser release --snapshot --clean, EXIT 0):

  • 6 archives built (linux/darwin/windows × amd64/arm64).
  • Each archive contains: workflow-plugin-agent binary (+ .exe on windows) + plugin.json + plugin.contracts.json + LICENSE.
  • checksums.txt with SHA-256 for all 6 archives.
  • before hook correctly rewrote plugin.json version + URLs (verified via snapshot mutation).

Scope

Task 1 only. Does NOT touch orchestrator/ (Phase 2), ratchet, registry, or other repos. Does NOT bump minEngineVersion. Does NOT tag or merge (lead verifies + merges).

🤖 Generated with Claude Code

Switch the agent plugin from an in-process engine plugin to a distributable
external gRPC binary, mirroring workflow-plugin-infra.

Changes:
- cmd/workflow-plugin-agent/main.go: sdk.Serve → sdk.ServePluginFull
  (forward-compat for future CLI/hooks; nil cli+hooks for now).
- .goreleaser.yml: full cross-platform build (linux/darwin/windows ×
  amd64/arm64), CGO_ENABLED=0, ldflag -X Version, archives bundle
  plugin.json + plugin.contracts.json + LICENSE sidecars, checksums,
  release.github, before-hook rewrites plugin.json version+URLs.
- plugin.json: type builtin→external; reconcile capabilities.stepTypes
  to runtime truth (2→4: add step.agent_execute, step.provider_test);
  add homepage/repository/downloads for the URL-rewrite hook. version
  stays 0.0.0 dev-sentinel (goreleaser injects the release tag).
- plugin_contracts_test.go: drop the now-wrong assertion that forbade
  step.agent_execute/step.provider_test from plugin.json stepTypes —
  that encoded the drift; capabilities must list ALL runtime steps.
- provider_manifest_test.go (new): asserts *AgentPlugin satisfies
  sdk.{PluginProvider,ModuleProvider,StepProvider,TypedModuleProvider,
  TypedStepProvider,ContractProvider}; Manifest() returns canonical
  name; and plugin.json stepTypes/moduleTypes == runtime (P6 drift gate).

No adapter file is needed: *AgentPlugin already implements every sdk
provider interface (Manifest via plugin.go:123; Step/Module/Typed via
typed_contracts.go). The plan's "adapter adds Manifest() only" was
already satisfied — this just wires the existing provider into a binary.

Verified:
- GOWORK=off go build ./... + go test ./... green (3 new tests pass).
- wfctl plugin verify-capabilities (built from workflow origin/main):
  OK workflow-plugin-agent 0.10.0 — runtime GetManifest matches plugin.json
  (4 steps + agent.provider) when ldflag is injected.
- goreleaser release --snapshot --clean: 6 archives built, each containing
  binary + plugin.json + plugin.contracts.json + LICENSE + checksums.txt.

Task 1 of the locked "agent plugin gRPC binary" plan.

Co-Authored-By: Claude <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings June 29, 2026 21:16

Copilot AI left a comment

Copy link
Copy Markdown

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 transitions workflow-plugin-agent from an in-process/builtin plugin shape to a distributable external gRPC plugin binary, aligning runtime-advertised capabilities with plugin.json and adding release packaging via GoReleaser.

Changes:

  • Switch the agent binary entrypoint to sdk.ServePluginFull(...) to support future CLI/hook extension points.
  • Update plugin.json for type: external, add release download metadata, and reconcile advertised capabilities.stepTypes with runtime (now 4 steps).
  • Add/adjust tests to prevent drift between runtime provider capabilities and plugin.json, and update GoReleaser config for cross-platform builds + release artifacts.

Reviewed changes

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

Show a summary per file
File Description
provider_manifest_test.go New drift-gate tests for provider interface satisfaction and runtime/plugin.json capability alignment.
plugin.json Mark plugin as external, add homepage/repo/downloads metadata, and expand capabilities.stepTypes to match runtime.
plugin_contracts_test.go Remove the legacy-step prohibition and instead assert non-empty capability advertisement.
cmd/workflow-plugin-agent/main.go Use sdk.ServePluginFull with nil CLI/hooks and build-version injection.
.goreleaser.yml Cross-platform build + packaging, version/URL rewrite hook, checksums, and GitHub release config.
.gitignore Ignore GoReleaser dist/ output and plugin.json.bak backup.

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

Comment thread provider_manifest_test.go
Comment on lines +136 to +154
runtime := map[string]bool{}
for _, s := range New().StepTypes() {
runtime[s] = true
}
declared := map[string]bool{}
for _, s := range manifest.Capabilities.StepTypes {
declared[s] = true
}

for s := range runtime {
if !declared[s] {
t.Fatalf("plugin.json stepTypes missing runtime step %q (drift: plugin.json must list every StepTypes() entry)", s)
}
}
for s := range declared {
if !runtime[s] {
t.Fatalf("plugin.json stepTypes declares %q but runtime StepTypes() does not return it", s)
}
}
Comment thread provider_manifest_test.go
Comment on lines +156 to +168
if len(manifest.Capabilities.ModuleTypes) == 0 {
t.Fatal("plugin.json capabilities.moduleTypes is empty")
}
runtimeMods := map[string]bool{}
for _, m := range New().ModuleTypes() {
runtimeMods[m] = true
}
for _, m := range manifest.Capabilities.ModuleTypes {
if !runtimeMods[m] {
t.Fatalf("plugin.json moduleTypes declares %q but runtime ModuleTypes() does not return it", m)
}
}
}
…tors

The strict-contracts coverage gate (wfctl plugin validate --strict-contracts)
requires every plugin.json capabilities.stepTypes entry to have a strict
contract descriptor. step.agent_execute and step.provider_test were registered
as legacy/untyped, so the gate failed with missing_step_contract_descriptor
for both.

Add strict contract DESCRIPTORS for both steps to ContractRegistry() and
plugin.contracts.json. The proto messages already existed
(AgentExecuteConfig/Input/Output, ProviderTestConfig/Input/Output) — only the
descriptor wiring was missing.

These two steps remain on the legacy map execution path: their implementations
lazy-lookup services (agent-provider-registry, agent-tool-registry) from
modular.Application, which the typed gRPC handler signature
(TypedStepHandler[C,I,O]) cannot access. CreateTypedStep therefore still
declines them (ErrTypedContractNotHandled) and TypedStepTypes() still lists
only the two fully-typed steps (provider_models, model_pull). The new
descriptors are honest about the I/O shape; the runtime path is unchanged.

This is the 'descriptor coverage != typed execution' distinction: a step can
carry a strict contract descriptor (so consumers + the gate know its shape)
while still executing via the legacy path when it needs host services the
typed contract cannot deliver.

Verification:
- GOWORK=off go build ./... && go test ./... green
- /tmp/wfctl plugin validate --file plugin.json --strict-contracts -> OK (0 errors; was 2)
- /tmp/wfctl plugin verify-capabilities (ldflag binary, temp v0.10.0) -> OK (0 mismatches)

Co-Authored-By: Claude <noreply@anthropic.com>
@intel352
intel352 merged commit 542bfa3 into master Jun 29, 2026
5 checks passed
@intel352
intel352 deleted the feat/agent-grpc-binary branch June 29, 2026 21:40
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.

2 participants