Important flows
This section describes the logic of each cargo agents command.
Crate-sourced skill resolution
A plugin loads a crate as a plugin by naming that crate in a [[plugins]] chained reference (source.cargo = "..."); the user can also load one directly by enabling the dependency it lives in (see enablement below). When the owning plugin is active and the edge’s predicates hold, the crate is resolved into the active plugin set — the shared list every facet (skills, MCP servers, hooks, subcommands) resolves over, so a crate-sourced plugin’s extensions dispatch exactly like a registry plugin’s. A single path handles every crate — a crate is always a first-class plugin, whether it describes itself with a SYMPOSIUM.toml, with [package.metadata.symposium], with both, or with neither:
skills::active_pluginsseeds a worklist from the trust-root plugins the registry loaded: each active plugin’splugin.chainededges whose predicates hold (evaluated against the owning plugin’s provenance) contribute asource.cargocrate id.- For each id the fixed-point calls
pms.load_plugin(id)on the package-manager setactive_pluginswas handed (built once bypackage_managers(deps)). The id’spmroutes it to the cargo transport, which:CargoPm::fetchresolves the source viaRustCrateFetch(path overrides for local path deps, then the cargo registry cache, then crates.io) withUpdateLevel::None— cache-only, so this is safe on the per-event hook path. The fetched id carries the exact resolved version.plugins::load_crate_manifestbuilds the plugin definition by layering three sources (merge order: crate defaults →[package.metadata.symposium]fromCargo.toml→SYMPOSIUM.tomlfile). Both manifest sources use the ordinary plugin-manifest schema and are parsed leniently (a malformed layer is logged and dropped). Validation runs underManifestOrigin::Crate(name defaults to the crate,depends-onis waived,[defaults]accepted, defaultskills/group appended unless[defaults] skills = false). The result is aParsedPluginwhosecanonicalid is the resolved crate. A crate with no manifest sources still yields one whose only content is that defaultskills/group.
record_activehonors the crate plugin’s own plugin-level predicates (applies, which stamps its provenance — never a workspace member), appends it to the active set, and enqueues its own[[plugins]]edges. This is how a[package.metadata.symposium]redirect (now a[[plugins]] source.cargochained reference to the target crate) is followed. Avisitedset keyed on(pm, normalized name)— global across the wholeactive_pluginscall — collapses diamonds (a crate reached through two plugins loads once, so its hooks don’t double-fire and its subcommands don’t read as a false conflict) and breaks cycles; the finite crate universe bounds termination.- Facet extraction then walks the active set.
collect_skillsruns each plugin’s skill groups through the ordinaryload_skills_for_grouppipeline — honoring named groups, group predicates, andsource.path/source.git, with each discovered skill’s origin hashed from its on-diskSKILL.mdpath (this is where git skill sources are fetched, hence theupdatelevel). MCP-server filtering (sync), hook dispatch (hook::dispatch_plugin_hooks), and subcommand lookup (subcommand_dispatch) each iterate the same set. A crate plugin’s custom predicate definitions are the one facet still not wired in — they resolve only from configured registries, andwarn_undispatched_crate_featuresnotes when a crate declares one.
A skill’s install identity is the hash of its on-disk SKILL.md path, so a crate reached two ways dedupes to one install. The edge’s version requirement is recorded but not yet enforced — the crate resolves against the workspace (pin / path override).
The key code paths are in pm/cargo/mod.rs (CargoPm::load_plugin, build_from_fetched), plugins.rs (load_crate_manifest, RawPluginManifest::merge, ManifestOrigin::Crate, ParsedPlugin::canonical), skills.rs (active_plugins, record_active, plugin_key, collect_skills, hash_origin_key), crate_metadata.rs (symposium_metadata), pm/cargo/workspace.rs (WorkspaceDeps, WorkspaceCrate), and crate_sources/mod.rs (RustCrateFetch).
Dependency enablement
A dependency’s own plugin content — a SYMPOSIUM.toml, [package.metadata.symposium], or a skills/ directory — is reachable without any manifest pointing at it, but only with the user’s consent: dependencies are not a trust root.
discovery::discoverasks the untrusted cargo transport for itsactive_plugins(dep_ids): the plugins embedded in the workspace’s dependencies.CargoPm::active_pluginsfetches each dependency cache-only and inspects it — a workspace dep resolves into the sourcecargo metadataalready extracted (WorkspaceCrate::source_dir), no probe/network — so registry-dep embedded plugins are discoverable too. The trusted registries (including the recommendations repo) are skipped, because their plugins are trust roots and never need consent. Each candidate is classified against[plugins]on its crate name — enabled byuse, auto-enabled, declined, or an undecided candidate. Nothing is prompted or written.- At sync time,
skills::active_pluginsasksdiscovery::enabled_dependencieswhich crate names[plugins] auto-enableor an applicableuseentry covers — workspace deps, plusused crates that aren’t deps at all — and seeds each as a cargo id on the same worklist a chained reference feeds, sopms.load_pluginhonors the crate’s manifest sources, skill groups, and its own[[plugins]]edges. This reads config rather than the offer list, socargo agents use <crate>loads a crate from crates.io whether or not the workspace depends on it, and even before its source has been fetched. (CargoPm::searchis what letsusename such a crate; a name a configured registry already provides is skipped here so it isn’t double-loaded.) - Independently, a registry plugin with no dependency gate anywhere loads dormant (
Plugin::requires_use) and activates only when auseentry names it. The gate rides thePredicateContext(with_used_names/is_used), so skill resolution, hook dispatch, subcommand lookup, help, and MCP filtering all agree.
The consent prompt and the use / search / status commands that record decisions are not implemented yet — today the [plugins] config is edited by hand.
The key code paths are in discovery.rs, config.rs (PluginsConfig, UseEntry), pm/cargo/mod.rs (active_plugins, load_plugin), plugins.rs (Plugin::requires_use), predicate.rs (PredicateContext::is_used), and skills.rs (active_plugins, record_active).
Help rendering
cargo agents --help (and -h, the bare help keyword, or no subcommand) is rendered by help_render, not by clap’s default help.
- The binary and the test harness parse argv with
Cli::try_parse_from, then callhelp_render::help_text(parse, args, sym, cwd). Because the decision happens after parsing, argument order (--help --quiet) does not matter and there is no second argv parser to keep in sync. - For no subcommand,
--help/-h, or the barehelpkeyword,help_textreturns the top-level grouped help:renderslices clap’s own rendered help (header + options block) and hand-renders “Commands for humans” / “Commands for agents” between them, mixing built-ins (cli::builtin_audience) with workspace-filtered plugin subcommands (subcommand_dispatch::applicable_subcommands). - For
<built-in> --help,help_textre-renders clap’s per-command help by walking clap’s command tree to the named subcommand — so required-arg commands (crate-info), required-subcommand groups (plugin), and nested commands (plugin list) all work even though clap’s auto help flag is disabled. - A plugin-vended
<name> --helpis left alone:help_textreturnsNone, and dispatch forwards--helpto the child binary, which owns its own help.
clap’s auto help flag and help subcommand are disabled in cli::Cli; --help/-h is a manual global bool. The key code paths are in help_render.rs (help_text, render, subcommand_help), cli.rs (builtin_audience, the Cli flags), and bin/cargo-agents.rs plus symposium-testlib (the parse-then-help_text wiring).
Subcommand dispatch
When the user runs cargo agents <name> for a name not built into the binary, clap’s allow_external_subcommands routes it to Commands::External(argv).
- The binary (or library
cli::run) callssubcommand_dispatch::dispatch_external(sym, cwd, argv), which first resolves the active plugin set (skills::active_plugins— registry plugins plus crate-sourced ones) so a crate’s subcommands are dispatchable too. find_subcommandwalks that set. For each plugin it applies the plugin-leveldepends-onpredicate against the workspace, then looks upargv[0]inplugin.subcommands. If the entry has its owndepends-onpredicate, that must also match. Two or more matches → error.- The matched subcommand’s
commandfield names anInstallationon the same plugin.installation::resolve_runnableacquires the source if any, runsinstall_commands, and picks theRunnable(Execfor binaries,Scriptfor shell scripts). - The child is spawned with stdio inherited. Its exit code is collapsed to a
u8— the binary wraps it inExitCode::from; the library treats non-zero as an error so the test harness can assert on success/failure.
The key code paths are in subcommand_dispatch.rs, cli.rs (the External arm), and bin/cargo-agents.rs (binary-side wrapping that surfaces the numeric exit code to the OS).