Skip to content

Add configurable agent, MCP bridge, and Open Plugin Spec v1 support - #1

Draft
baladithyab wants to merge 1 commit into
mainfrom
claude/agent-adapter-framework-1KOmV
Draft

Add configurable agent, MCP bridge, and Open Plugin Spec v1 support#1
baladithyab wants to merge 1 commit into
mainfrom
claude/agent-adapter-framework-1KOmV

Conversation

@baladithyab

Copy link
Copy Markdown
Contributor

Summary

This PR introduces a declarative configuration system for VIGOR agents, enabling multi-adapter composition, MCP server integration, and Open Plugin Spec v1 compatibility. It adds two new packages (vigor-agent and vigor-mcp) and extends the core with agent configuration schemas and plugin helpers.

Key Changes

New Packages

  • vigor-agent: Configurable VIGOR agent that loads AgentConfig (YAML/JSON), instantiates adapters and backends, routes tasks across multiple adapters, and manages a run archive. Includes:

    • AgentOrchestrator: thin wrapper wiring config to the runtime
    • AdapterRegistry: instantiate and look up adapters by ID
    • Router: resolve tasks to adapters via modality/domain matching or explicit routing
    • FactoryRef loader with allowlist-based supply-chain gating
    • Plugin discovery for Open Plugin Spec v1 directories
    • CLI for running tasks against agent configs
  • vigor-mcp: Bridge exposing Model Context Protocol servers as a ToolBackend. Supports stdio, HTTP, and SSE transports via the official MCP SDK. Sessions are lazily opened and held for the agent lifetime to amortize connection costs.

Core Extensions (vigor-core)

  • AgentConfig schema (vigor.agent_config.v1): Declaratively wires adapters, backends, MCP servers, and routing policies

    • AdapterSpec: factory ref + modalities/domains
    • BackendSpec: factory ref for per-task backend instantiation
    • MCPServerSpec: stdio/HTTP/SSE server declarations with tool allowlists
    • RoutingPolicy: modality_match / domain_match / explicit / single strategies with per-task overrides
  • Plugin helpers (vigor_core.plugin):

    • OpenPluginManifest: Pydantic model for Open Plugin Spec v1 core fields
    • export_plugin_json(): serialize manifest to JSON
    • export_skill_md(): auto-generate SKILL.md from registered IR schemas (guards against drift)
    • SkillTemplate: dataclass for skill metadata
  • IR registry (vigor_core.registry):

    • register_ir(): register IR models for schema export
    • get_ir_model(): retrieve registered model by kind
    • export_json_schema(): generate JSON Schema from IR model
  • RunContext enhancement: added optional tools: ToolBackend field for ambient tool access

Adapter Updates

All three adapters now ship Open Plugin Spec v1 manifests:

  • .plugin/plugin.json: declares skills and metadata
  • .plugin/vigor.json: Python factory ref for VIGOR loop
  • Auto-generated SKILL.md files in skill directories (photo-edit-recipe, cad-parametric, manim-scene)

Each adapter's IR module now calls register_ir() at import time to enable schema export.

Testing & Tooling

  • Comprehensive test suites for agent config validation, factory loading, routing resolution, plugin discovery, and MCP backend
  • scripts/regen_skills.py: regenerate all SKILL.md files from IR schemas (CI guard against drift)
  • Updated vigor-runtime orchestrator to accept optional ToolBackend in RunContext

Documentation

  • ADR-0014: Generalized Agent Configuration (rationale for AgentConfig and multi-adapter composition)
  • ADR-0015: Open Plugin Spec v1 Compatibility (dual-publishing as Python packages + plugins)

Implementation Details

  • Factory loading mirrors vigor_harness.evaluator pattern with allowlist gating for supply-chain security
  • MCP sessions use AsyncExitStack for clean lifecycle management; one session per server, held for agent lifetime
  • Plugin discovery resolves .plugin/plugin.json + optional .plugin/vigor.json and on-disk skill/MCP paths
  • SKILL.md generation is deterministic from JSON Schema, enabling CI validation that host-agent skills never drift from typed VIGOR contracts
  • Routing supports task-level overrides via RoutingPolicy.overrides dict

https://claude.ai/code/session_0178GXqYgTfRDxKVm19UWV3T

…ility

Ship `vigor-agent` (configurable agent with adapter registry, router,
CLI) and `vigor-mcp` (MCP-as-ToolBackend bridge over stdio + http/sse)
so one VIGOR agent can be wired declaratively from a YAML/JSON config
instead of forking the runtime per use-case.

Add Open Plugin Spec v1 manifests + generated SKILL.md to each adapter
package so the same package also works as a cross-vendor plugin in
Claude Code, Hermes, Strands, Goose, etc., without giving up the
verifiable iterative loop.

ADR-0014 covers the AgentConfig / Router design; ADR-0015 covers the
plugin compatibility layer.

https://claude.ai/code/session_0178GXqYgTfRDxKVm19UWV3T
baladithyab added a commit that referenced this pull request May 16, 2026
Closes ADR-0016 §3.2's gap: mutator tools were policy-only on the schema
(ToolManifest.mutability) but no code enforced "mutators require capability
grants". Per scout finding #3 (.overstory/specs/VIGOR-4293.md §4) and
threat-model T4 (docs/security/threat-model.md:280) this was the #1
preventive mitigation for an operator allowlist mistake silently exposing
a destructive upstream tool (e.g. neka-nat/freecad-mcp `execute_code`).

Changes:
- vigor-core/interfaces.py:
  - Add `RunContext.tool_capabilities: frozenset[str]` (default-empty).
  - Extend `ToolBackend.call_tool` ABC with `*, capabilities: frozenset[str] | None = None`.
- vigor-mcp/backend.py:
  - `_ServerHandle.get_manifest` returns the cached `ToolManifest` for
    a tool_id (uses the existing `list_tools` cache; one-shot fetch).
  - `MCPToolBackend.call_tool` rejects mutator calls whose tool_id is
    not in `capabilities` with `ToolResult(status="failure")`. Observer
    tools always pass. Allowlist gate runs first (unchanged behavior).
  - Refactored gate logic into `_gate_call` helper to keep the dispatch
    path within the project's PLR0911 (≤6 returns) limit.
- vigor-runtime/orchestrator.py:
  - `Orchestrator(tool_capabilities=...)` constructor param threads
    through to the per-run `RunContext`. Default-deny: empty frozenset.
- Test backends (NullToolBackend, _SpyTools, _RecordingToolBackend,
  _CountingToolBackend, _MinimalTools): updated `call_tool` signatures
  to match the new ABC.
- Tests:
  - test_backend.py: 6 new mutator-gate tests — observer always-passes,
    mutator denied without/with-empty/with-mismatched cap, mutator
    passes with matching cap, denial does not roundtrip the server.
  - test_orchestrator.py: 2 new tests — default capabilities are empty,
    constructor-passed capabilities surface on `RunContext`.

Quality gates: 156 passed / 1 skipped; ruff clean; mypy clean.
baladithyab added a commit that referenced this pull request May 16, 2026
…ADR-0035 §Negative #1, VIGOR-c2ec)

Layered in-process guardrail on top of VIGOR-aa1c's cross-process advisory
lock. A second Orchestrator.run / .resume on the same archive root within
one process now raises ArchiveBusyError immediately, before any archive I/O.

- vigor_core.errors.ArchiveBusyError: sibling of ArchiveLockedError, not a
  subclass — busy = same-process re-entry (caller error), locked = peer
  process holds OS lock (potentially transient). Distinct retry semantics.
- vigor_core.archive._ActiveRunRegistry: threading.Lock-protected set of
  resolved archive roots, atomic check-and-insert. Mirrors _LockRegistry's
  pattern (mx-7ce41e). Re-exported from vigor_core to match
  ArchiveLockedError's public surface convention.
- RunArchive.claim_active_run(): contextmanager idiom — release on every
  exit path including exceptions. Adapter export() paths constructing
  transient RunArchive(run_dir.parent) are unaffected (mx-514b28); the
  guard is at Orchestrator.run, not RunArchive.__init__.
- Orchestrator.run / .resume: 'with self._archive.claim_active_run():'
  wraps both archive I/O (write_task / read_checkpoint) and _execute,
  so busy fires before write_task per AC #2.
- Tests: 5 new — concurrent gather raises busy, sequential succeeds,
  export-during-run still works, marker released on failure, sibling
  type relationship.

codex review: confirmed (a) correct sibling typing, (b) export path
preserved, (c) thread-safe under asyncio. No P0/P1 findings.
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