Skip to content

feat(skills): Agent Skills support with orchestrator-driven activation#954

Open
Spherrrical wants to merge 2 commits into
mainfrom
musa/agent-skills-support
Open

feat(skills): Agent Skills support with orchestrator-driven activation#954
Spherrrical wants to merge 2 commits into
mainfrom
musa/agent-skills-support

Conversation

@Spherrrical
Copy link
Copy Markdown
Collaborator

Summary

Integrates Agent Skills into Plano as a first-class capability: skills are discovered from .plano/skills, ~/.plano/skills, and the universal ~/.agents/skills (where npx skills add installs), attached to routing_preferences[].skills allow-lists, selected per-request by Plano-Orchestrator alongside the existing <routes> block, and injected as <skill_content> wrappers into the upstream system prompt — all native, no WASM hop, no synthetic tool calls.

Two commits, second one addresses code-review feedback on the first:

7f5bf641 — feature

  • CLI (cli/planoai/): planoai skills add | list | remove | trust, three discovery scopes (project requires explicit trust; user and agents are auto-trusted), lenient SKILL.md parsing with diagnostics, snapshot-diff post-install attribution so npx-installed skills are picked up regardless of where they land.
  • Config (config/plano_config_schema.yaml): documents model_metrics_sources (cost / latency providers) since selection_policy.prefer: cheapest|fastest now requires it at startup. routing_preferences[].skills is a typed allow-list.
  • Rust runtime (crates/common/src/skills_runtime.rs, crates/brightstaff/): SkillRef config model, orchestrator V1 system prompt extended with a <skills> XML block (parsed as {"route": [...], "skills": [...]}), OrchestratorService::with_routing_and_skills seeds the catalog from the union of every route's allow-list, and the LLM handler prepends activated skill bodies into the upstream request before serialization.
  • Docs (docs/source/resources/skills.rst): end-to-end walkthrough, scope precedence table, and routing flow.
  • Template (cli/planoai/templates/skills_routing.yaml): ready-made planoai init --template skills_routing bootstrap.

5e8d27f — review fixes

  • Skills-only decisions are honored. RouteDecision.route_name is now Option<String>; when the orchestrator returns {"route": [], "skills": [...]} the request falls back to the originally-requested model and skills still inject, matching the contract documented in skills.rst:153-155.
  • Drops are visible. common::skills_runtime::resolve_for_route returns a SkillResolution that splits drops into dropped_not_allowed vs dropped_unknown; brightstaff logs each via dedicated warn!s with the offending route + skill names — no more silent filtering.
  • Single source of truth. common::skills_runtime owns referenced_skills_catalog, resolve_for_route, resolve_selected_skills, and augment_system_prompt_with_skills. brightstaff's parallel re-implementations are gone.
  • Behavior contract is documented + tested. inject_activated_skills_into_request now spells out the first-system-message rule and the Parts → Text flatten; 6 new tests cover both. 11 new tests in skills_runtime cover catalog union, allow-list intersection, dedup, hallucination handling, XML escape, and body cap.
  • Hardening. MAX_SKILL_BODY_BYTES = 32 KiB per-skill UTF-8-safe tail-trim guards the downstream context window; skill name / base_dir are XML-attribute-escaped in the <skill_content> wrapper as defense-in-depth; cli/planoai/skills.py::find_project_root bounds the walk at \$HOME (plus a 30-parent hard cap) so it no longer climbs to /.

Wire shape (orchestrator system prompt, abbreviated)

<routes>{route_name, description, ...}</routes>
<skills>{name, description}</skills>
<conversation>[...]</conversation>

Respond strictly as: {"route": ["..."], "skills": ["..."]}

Activated SkillRef bodies are then wrapped into the upstream system prompt:

<skill_content name="requesting-code-review" base_dir="/Users/me/.agents/skills/requesting-code-review">
…SKILL.md body…
</skill_content>

Test plan

  • cd crates && cargo fmt --all -- --check (passed locally — pre-commit hook)
  • cd crates && cargo clippy --locked --all-targets --all-features -- -D warnings (passed locally)
  • cd crates && cargo test --lib — 53 common + 173 brightstaff (passed locally)
  • cd cli && uv run pytest -v — 123 tests (passed locally)
  • End-to-end with digitalocean/anthropic-claude-opus-4.7 + requesting-code-review skill: orchestrator picks the skill, brightstaff logs injecting activated Agent Skills into upstream system prompt, and the model returns a structured code review. Verified locally with a 30+ KB streaming git diff payload (stream: true + curl -N).
  • Skills-only path: send a prompt that matches a skill but no route → brightstaff logs no route determined; activating skills against default model and the originally-requested model is used.
  • Allow-list-mismatch warn: configure two routes with different skills: lists, prompt cross-route, confirm warn!(skills not on this route's allow-list) appears.
  • planoai skills add openai/skills followed by planoai up from outside the plano repo — config templates resolve, brightstaff loads the catalog, skills appear in the orchestrator <skills> block.

… helpers, warn on dropped picks

Addresses the code-review findings on 7f5bf64:

- Honor skills-only decisions: RouteDecision.route_name is now Option<String> and the orchestrator emits a decision when routes is empty but skills is non-empty. The LLM handler falls back to the originally-requested model and still injects activated skill bodies, matching the contract in docs/source/resources/skills.rst.
- Warn on allow-list misses: resolve_for_route now returns a SkillResolution that splits drops into "not allow-listed for this route" vs "not in catalog (hallucinated)". brightstaff logs each bucket so misconfigured routing_preferences[].skills lists become visible instead of vanishing silently.
- Consolidate runtime: common::skills_runtime is now the single source of truth (referenced_skills_catalog, resolve_for_route, resolve_selected_skills, augment_system_prompt_with_skills). brightstaff drops its local re-implementations and calls into common.
- Tests: 11 new tests in common::skills_runtime (catalog union, allow-list intersection, dedup, hallucination handling, XML escaping, body size cap) and 6 new tests in brightstaff::handlers::llm::model_selection cover inject_activated_skills_into_request, including the first-system-message rule and the Parts->Text flatten — both now documented on the function.
- Cap skill body size at 32 KiB with a UTF-8-safe tail-trim + marker so an oversized SKILL.md cannot blow the downstream context window.
- XML-escape skill name and base_dir in the <skill_content> wrapper as defense-in-depth (names are validated upstream, but the wrapper sits inside the system prompt).
- Bound find_project_root at \$HOME plus a 30-parent depth cap so CLI invocations outside HOME no longer walk to /.
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