Skip to content

Crate forward2#249

Closed
nikomatsakis wants to merge 17 commits into
symposium-dev:mainfrom
nikomatsakis:crate-forward2
Closed

Crate forward2#249
nikomatsakis wants to merge 17 commits into
symposium-dev:mainfrom
nikomatsakis:crate-forward2

Conversation

@nikomatsakis

Copy link
Copy Markdown
Member

What does this PR do?

Registry-ready plugin source system

Replace the git-repository-based plugin source model with a registry-based system where path, git, and crate registries each resolve plugin source declarations into concrete source trees for Symposium to scan.

What changed

New config model — Plugin sources are now installed via [installed.crates] (Cargo dependency syntax), installed.paths, and installed.git, replacing the old [[plugin-source]] and [defaults] config. The default install is symposium-recommendations = "1" as a crate, not a git source.

Install/uninstall commandscargo agents install <CRATE>[@<VERSION>], --path, --git and cargo agents uninstall let users manage installed sources from the CLI.

Source graph with provenance — A ResolvedSourceGraph tracks how each source was reached (installed, workspace, dependency) with non-exclusive provenance flags. workspace(), dependency(), and installed() predicates evaluate against these flags, enabling skills that only activate for crate developers vs. downstream users.

Discovery policy — Plugins can declare [discovery.allow] / [discovery.deny] rules to curate which workspace dependencies are auto-discovered as plugin sources. Specificity-based matching: specific beats wildcard, deny wins ties.

Recursive plugin sources[[plugins]] source.crate, source.git, and source.path declarations are followed transitively with cycle detection and provenance propagation.

Implicit binary installations — Crate binary targets from Cargo.toml are automatically available as installations, referenced by name in hooks/subcommands with no explicit [[installations]] block needed.

Status commandcargo agents status exposes the full resolved state (installed sources, active/inactive plugins, predicate explanations, configured agents) without mutating skill directories.

Unified registry path — Help rendering, hook dispatch, subcommand dispatch, and MCP registration all use the same graph-based resolution as sync.

Legacy removal[[plugin-source]], [defaults], source = "crate" skill groups, [package.metadata.symposium], and the old provider commands are removed with schema-level rejection.

Smaller improvements

  • Canonicalize crate registry paths so cross-registry dedup works through symlinks
  • warn.if_empty field on [[skills]] groups (defaults suppress the "no SKILL.md" warning; explicit groups warn; authors can opt out)
  • symposium-sdk crate with WorkspaceDeps caching and SymposiumDirs for shared path resolution

Test plan

  • 438 unit + integration tests pass (337 lib, 60 init_sync, 9 help_render, etc.)
  • cargo agents plugin validate runs clean against migrated symposium-recommendations
  • cargo clippy --all --workspace clean (pre-existing warnings only)
  • Manual verification: cargo agents sync with local symposium-recommendations path source resolves and installs skills correctly
Disclosure questions

AI disclosure.

  • The AI tool authored large parts of the code

Questions for reviewers.

nikomatsakis and others added 14 commits June 24, 2026 19:10
Rewrite user-facing docs and design docs to reflect the proposed
crate-forward plugin model: installed crates, dependency allow lists,
auto-install, workspace as plugin source, and new predicates.

Co-authored-by: Claude <claude@anthropic.com>
  Completed:

  - Phase 3 source acquisition: path/git/crate registry resolver and
    Cargo dependency-table probing.

  - Phase 3.5 source graph API: installed/workspace provenance, dedupe,
    workspace members.

  - Phase 4 discovery model: synthesized root manifests, default
    skill/plugin declarations, optional names, default where.crates =
    ["*"], shared where parsing, [[plugins]] source.path/git/crate
    declarations, nested independent manifests, fixture migration to
    skills/.

  Verification:

  - cargo fmt --check
  - cargo test passed fully.

  Deviation audit:

  - Phase 3 per-source freshness metadata remains deferred; existing git/
    Cargo cache behavior covers current callers, and repeated graph
    resolution is not wired until later phases.

  - Phase 3.5 dependency provenance is represented but automatic
    dependency expansion is deferred to Phase 7 as planned.

  - Phase 4 parses/retains git and crate plugin-source declarations but
    only resolves path declarations, matching the Phase 7 deferral.

  - workspace() is represented as a built-in predicate now; full source-
    context semantics remain for Phase 5.

Co-authored-by: Codex codex@openai.com (codex@openai.com)
Phase 5: Add `dependency()` and `installed()` predicates alongside
`workspace()`. All three evaluate against a per-plugin provenance set
(`BTreeSet<SourceProvenance>`) on `PredicateContext`, updated before
each plugin's predicates are evaluated. Thread provenance through all
call sites: skills, hooks, MCP servers, subcommands.

Phase 6: Wire sync to use `ResolvedSourceGraph`. New
`resolve_sync_sources` combines installed sources (best-effort),
workspace root/members, and legacy `[[plugin-source]]` entries.
`load_registry_from_graph` scans each node, stamps provenance, and
deduplicates manifests by canonical path (unioning provenance when
the same SYMPOSIUM.toml is reached from multiple roots).

Key design decisions:
- Workspace root only added when it has an explicit SYMPOSIUM.toml
  (avoids unbounded recursive search through project tree)
- `agents-syncing = false` disables workspace source scanning
- Installed crate resolution is best-effort (warns and skips)
- Hooks/help/subcommands remain on legacy `load_registry` for now

Co-authored-by: Claude <claude@anthropic.com>
Add specificity-based allow/deny policy evaluation for workspace
dependency auto-discovery, and iterative graph expansion that follows
recursive [[plugins]] source.crate/source.git declarations.

Key changes:
- New `discovery.rs` module with CollectedPolicy engine (3-tier
  specificity: global wildcard < registry wildcard < specific crate)
- `expand_source_graph()` in crate_sources iterates until convergence:
  collects policy from resolved plugins, evaluates workspace dep
  candidates, follows recursive source declarations
- Plugin manifests can declare [discovery.allow]/[discovery.deny]
- Sync calls expand_source_graph after initial graph construction

Tests: 10 unit tests for policy logic, 3 integration tests proving
discovery-allow installs, recursive source.crate resolves, default
deny blocks, and workspace() predicate gates recursive provenance.

Co-authored-by: Claude <claude@anthropic.com>
- Cycle termination: A source.crate→B, B source.crate→A doesn't loop
  and both skills install
- Chained expansion: discovery allows mid-crate, mid-crate's [[plugins]]
  source.crate resolves leaf-crate, leaf skill installs
- Provenance growth: plugin-x is both installed and discovered as dep,
  dependency()-gated skill installs because provenance is unioned

Co-authored-by: Claude <claude@anthropic.com>
Replace the round-based iteration (which re-scanned all nodes each pass)
with a simple worklist: push specs to resolve, pop and process one at a
time. A scanned set tracks (path, provenance) so we only re-process a
node when new provenance arrives.

Key properties:
- Each node is scanned at most once per provenance combination
- Cycle termination: A→B→A resolves B, sees A already scanned, stops
- Provenance propagation: when discovery adds Dependency to an already-
  Installed node, children are re-pushed with the grown provenance
- Policy growth: when a newly-scanned plugin contributes allow rules,
  workspace dep candidates are immediately re-evaluated
- Safety limit of 1000 iterations (far above any real graph)

No behavioral changes — all 6 expansion integration tests pass unchanged.

Co-authored-by: Claude <claude@anthropic.com>
Plugin hooks and subcommands can now reference crate binary targets
by name without an explicit [[installations]] entry. After validating
the manifest, `merge_implicit_installations` reads `Cargo.toml` from
the manifest's directory and adds:

- One Installation per [[bin]] target (or the inferred default target)
- A `crate` alias pointing to the package-name binary

Explicit installations always take precedence — implicit entries are
only added for names not already declared.

Tests: parse_binary_targets (explicit + inferred), implicit merge with
`crate` alias, explicit-takes-precedence.

Co-authored-by: Claude <claude@anthropic.com>
New read-only command that shows the resolved plugin/skill state
without installing anything. Resolves the same source graph as sync
(installed + workspace + legacy + discovery + recursive), evaluates
predicates, and reports:

- Source nodes with provenance (installed/workspace/dependency)
- Active/inactive plugins and skill groups
- Summary (source count, plugin count, skill count, agent count)
- Configured agents

Supports --json for structured output via the existing report layer.

Tests: integration test proving status does NOT create skill dirs.

Co-authored-by: Claude <claude@anthropic.com>
Addresses discrepancies identified in plan review:

Discovery policy (Phase 7):
- Add 6 precedence tests covering all tier combinations:
  tier 0 vs tier 1 (registry beats global in both directions),
  tier 1 vs tier 2 (specific beats registry wildcard in both),
  and multi-source same-specificity (deny wins regardless of order)

Implicit installations (Phase 8):
- Move implicit binary injection into validate_manifest() so hooks
  and subcommands can reference them during validation (was a bug:
  post-validation merge meant hooks couldn't find implicit names)
- Add test: hook command = "helper-bin" resolves against [[bin]] target
- Add test: subcommand command = "crate" resolves to default binary
- Add implicit-binary0 fixture for potential integration use

Co-authored-by: Claude <claude@anthropic.com>
… rejection, status tests

1. Reject where/crates/predicates on [[plugins]] source.git/source.crate
   with a clear error (predicate-gated recursive sources are a planned
   future extension). Path sources still support predicates.

2. Extract ResolvedSourceGraph::build_initial() as the shared entry point
   for both sync and status, eliminating the duplicated build_source_graph
   in status.rs.

3. Add status JSON schema test: verifies status_source, status_plugin,
   status_summary events are present with correct shapes.

4. Add status inactive plugin test: verifies reporting works with
   workspace-noserde0 fixture.

Co-authored-by: Claude <claude@anthropic.com>
help_render, discovery_hint (hooks), and dispatch_external now use
ResolvedSourceGraph::build_initial + expand_source_graph +
load_registry_from_graph instead of the legacy load_registry(sym).

This means they see the same source model as sync/status: installed
sources, workspace members, discovery-allowed deps, and recursive
[[plugins]] source.* declarations — with full provenance.

help_text and render_help are now async (the binary main and testlib
are already async). register_hooks (called from init, no workspace)
remains on the legacy path until Phase 10 removes it.

Co-authored-by: Claude <claude@anthropic.com>
The crate registry resolver returned paths from cargo metadata without
canonicalizing, which could cause the same directory (reached via a
symlink) to appear as separate graph nodes when also installed through
the path registry. Add canonicalization in resolve_crate and a test
that exercises the symlink case.

Also includes Phase 10 completion: remove legacy [[plugin-source]],
[defaults], source="crate" mechanisms; switch help/hooks/subcommand
dispatch to graph-based registry; add predicate-gated recursive source
expansion; migrate all fixtures and docs to the new config model.

Co-authored-by: Claude <claude@anthropic.com>
Replace the internal is_default flag with a user-facing warn.if_empty
field on [[skills]] groups. Defaults to true (explicit groups warn when
empty), but the implicit default groups (skills/, .agents/skills/) set
it to false. Plugin authors can also set warn.if_empty = false on their
own explicit skill groups to suppress the warning.

Co-authored-by: Claude <claude@anthropic.com>
nikomatsakis and others added 3 commits June 26, 2026 15:00
On macOS, /var is a symlink to /private/var, so canonicalize() returns
a different path than tempfile provides. Update test assertions to
compare against canonicalized paths, matching the contract that
resolve_crate now guarantees.

Co-authored-by: Claude <claude@anthropic.com>
Rename the CLI commands (`cargo agents install` → `cargo agents use`,
`cargo agents uninstall` → `cargo agents remove`), the config section
(`[installed]` → `[used]`), and the `installed()` predicate to `used()`
for a consistent mental model throughout.

Co-authored-by: Claude <claude@anthropic.com>
…tion

Implement directory-scoped plugin sources so `cargo agents use` scopes
entries to the current workspace by default (opt out with `--global`).
Unify config and manifest plugin source resolution into a single
fixed-point loop that handles custom-predicate-gated entries correctly.

Key changes:
- New `directory()` predicate with exact and `/**` prefix forms
- Config schema migrated from `[used]` to `[[plugins]]` array-of-tables
  (legacy format silently upgraded on read)
- `--global` flag on `cargo agents use`; default is directory-scoped
- `expand_source_graph()` tracks custom predicate definitions and defers
  entries gated on unknown customs, retrying when definitions appear
- Fixes latent bug where custom predicates on [[plugins]] source
  declarations were silently skipped

Co-authored-by: Claude <claude@anthropic.com>
@nikomatsakis

Copy link
Copy Markdown
Member Author

This PR is too big. Going to revisit it with the #250 RFD process

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