Create Plugin Design Guide: codify architecture principles for reviewers and designers #59

Closed
opened 2026-02-22 11:08:01 +00:00 by nazim · 5 comments
Contributor

Problem

Cobot's plugin documentation is scattered and incomplete:

  • docs/architecture.md — has Mermaid diagrams for registry, message flow, extension points, hook chain. Good overview but doesn't explain when to use what or design principles.
  • CONTRIBUTING.md — has a basic "create a plugin" example with PluginMeta + extension points. Recipe-level, no design guidance.
  • README.md — marketing-level feature list.

What's missing:

  • When to use capabilities vs dependencies vs extension_points
  • When get_by_capability() is correct vs when to declare a hard dependency
  • The ToolProvider pattern and how to add tools to the agent
  • How to make dependencies visible in the plugin graph
  • Design principles that reviewers can check PRs against
  • Anti-patterns with concrete examples from our codebase

We just spent a session discovering these patterns empirically (#57, #58). They should be codified so future contributors (human and AI) don't rediscover them.

Proposed: docs/plugin-design-guide.md

A single authoritative document covering:

1. PluginMeta Vocabulary

Complete reference for every field and when to use it:

Field Meaning Use when Example
capabilities "I provide X" Your plugin offers a service others can discover ["wallet", "tools"]
dependencies "I require X" (hard) Plugin won't function without it ["config", "persistence"]
optional_dependencies "I can use X" Enhanced behavior if available, works without ["llm"] for security
consumes "I aggregate from capability group X" You call all_with_capability() ["tools", "llm"] for loop
implements "I fulfill this extension point" You provide a handler for another plugin's contract {"context.system_prompt": "get_soul"}
extension_points "I define this contract" You want other plugins to hook into your behavior ["loop.before_llm"]
priority Load order (lower = earlier) Foundation (1-5), Core (10-18), Services (20-30), Orchestration (50+)

2. Design Principles

Principle 1: Own your tools
If your plugin provides tools to the LLM agent, implement ToolProvider and declare capabilities: ["tools"]. Never add your tools to another plugin.

knowledge/plugin.py — implements ToolProvider, owns its tool definitions and execution
tools/plugin.py — hardcodes wallet tool definitions (being fixed in #58)

Principle 2: Declare all coupling
If your plugin calls get_by_capability() or all_with_capability(), declare it in PluginMeta via consumes or optional_dependencies. If it's not in the graph, it's not in the architecture.

Principle 3: Adding a plugin never requires editing another plugin
New ToolProviders appear automatically via capability discovery. New extension point implementations wire in via implements. If adding your plugin requires editing an existing one, the architecture is wrong.

Principle 4: Hard deps for requirements, capabilities for discovery

  • dependencies: ["config"] — plugin won't start without config
  • consumes: ["tools"] — plugin aggregates from whoever provides "tools"
  • get_by_capability("llm") — get whichever LLM provider is active (swappable singleton)
  • all_with_capability("tools") — collect from all ToolProviders (registry pattern)

Principle 5: No secrets from environment
Plugins should never read credentials via os.environ. Use the declared secrets field (once #50 lands). Until then, document env var requirements.

3. Patterns Catalog

Pattern: ToolProvider (existing, proven)
When: Your plugin wants to expose actions to the LLM agent.
How: Implement ToolProvider interface, add "tools" to capabilities.
Example: knowledge/plugin.py

Pattern: Swappable Provider (existing, proven)
When: Multiple plugins can fulfill the same role (e.g., ppq vs ollama for LLM).
How: Both declare same capability, consumer uses get_by_capability().
Example: ppq and ollama both declare capabilities: ["llm"]

Pattern: Extension Point Hook (existing, proven)
When: Your plugin defines a lifecycle event others should participate in.
How: Define in extension_points, others declare in implements.
Example: loop defines loop.before_llm, logger implements it.

Pattern: Capability Aggregation (existing, needs declaring)
When: Your plugin collects from all providers of a capability.
How: Use all_with_capability(), declare in consumes.
Example: loop consumes ["tools", "llm"]

4. Anti-Patterns

Anti-pattern: God Object Tool Registry
Symptom: One plugin hardcodes tool definitions for other plugins.
Fix: Each plugin owns its tools via ToolProvider.
Ref: #58 (tools plugin hardcoding wallet)

Anti-pattern: Hidden Service Locator
Symptom: get_by_capability() without declaring the relationship in PluginMeta.
Fix: Add consumes or optional_dependencies.
Ref: #57 (10 plugins with invisible coupling)

Anti-pattern: Environment Grab Bag
Symptom: os.environ.copy() or os.environ.get("SECRET") in plugin code.
Fix: Declare secrets in PluginMeta, receive via config injection.
Ref: #50 (wallet passes all env to subprocesses)

5. Reviewer Checklist

For every plugin PR, reviewers should check:

  • All get_by_capability() / all_with_capability() calls have corresponding consumes or optional_dependencies in PluginMeta
  • No tool definitions exist outside the plugin that owns them
  • No os.environ reads for secrets
  • New plugins don't require edits to existing plugins
  • ToolProvider used for any LLM-facing tools
  • Plugin graph accurately reflects all edges (cobot plugins inspect)

Acceptance criteria

  • docs/plugin-design-guide.md created with all 5 sections above
  • docs/architecture.md updated: link to design guide, add consumes to PluginMeta class diagram
  • CONTRIBUTING.md updated: link to design guide in Plugin Development section
  • Reviewer checklist added to PR template (if one exists)
  • Content reviewed against actual codebase — all examples reference real files
  • #58 — Tool injection refactor (concrete fix that these principles codify)
  • #50 — Secret management (principle 5)
  • #54 — Plugin introspection (graph must reflect reality)
## Problem Cobot's plugin documentation is scattered and incomplete: - **`docs/architecture.md`** — has Mermaid diagrams for registry, message flow, extension points, hook chain. Good overview but doesn't explain *when to use what* or *design principles*. - **`CONTRIBUTING.md`** — has a basic "create a plugin" example with PluginMeta + extension points. Recipe-level, no design guidance. - **`README.md`** — marketing-level feature list. **What's missing:** - When to use `capabilities` vs `dependencies` vs `extension_points` - When `get_by_capability()` is correct vs when to declare a hard dependency - The `ToolProvider` pattern and how to add tools to the agent - How to make dependencies visible in the plugin graph - Design principles that reviewers can check PRs against - Anti-patterns with concrete examples from our codebase We just spent a session discovering these patterns empirically (#57, #58). They should be codified so future contributors (human and AI) don't rediscover them. ## Proposed: `docs/plugin-design-guide.md` A single authoritative document covering: ### 1. PluginMeta Vocabulary Complete reference for every field and when to use it: | Field | Meaning | Use when | Example | |-------|---------|----------|---------| | `capabilities` | "I provide X" | Your plugin offers a service others can discover | `["wallet", "tools"]` | | `dependencies` | "I require X" (hard) | Plugin won't function without it | `["config", "persistence"]` | | `optional_dependencies` | "I can use X" | Enhanced behavior if available, works without | `["llm"]` for security | | `consumes` | "I aggregate from capability group X" | You call `all_with_capability()` | `["tools", "llm"]` for loop | | `implements` | "I fulfill this extension point" | You provide a handler for another plugin's contract | `{"context.system_prompt": "get_soul"}` | | `extension_points` | "I define this contract" | You want other plugins to hook into your behavior | `["loop.before_llm"]` | | `priority` | Load order (lower = earlier) | Foundation (1-5), Core (10-18), Services (20-30), Orchestration (50+) | ### 2. Design Principles **Principle 1: Own your tools** If your plugin provides tools to the LLM agent, implement `ToolProvider` and declare `capabilities: ["tools"]`. Never add your tools to another plugin. ✅ `knowledge/plugin.py` — implements ToolProvider, owns its tool definitions and execution ❌ `tools/plugin.py` — hardcodes wallet tool definitions (being fixed in #58) **Principle 2: Declare all coupling** If your plugin calls `get_by_capability()` or `all_with_capability()`, declare it in PluginMeta via `consumes` or `optional_dependencies`. If it's not in the graph, it's not in the architecture. **Principle 3: Adding a plugin never requires editing another plugin** New ToolProviders appear automatically via capability discovery. New extension point implementations wire in via `implements`. If adding your plugin requires editing an existing one, the architecture is wrong. **Principle 4: Hard deps for requirements, capabilities for discovery** - `dependencies: ["config"]` — plugin won't start without config - `consumes: ["tools"]` — plugin aggregates from whoever provides "tools" - `get_by_capability("llm")` — get whichever LLM provider is active (swappable singleton) - `all_with_capability("tools")` — collect from all ToolProviders (registry pattern) **Principle 5: No secrets from environment** Plugins should never read credentials via `os.environ`. Use the declared `secrets` field (once #50 lands). Until then, document env var requirements. ### 3. Patterns Catalog **Pattern: ToolProvider** (existing, proven) When: Your plugin wants to expose actions to the LLM agent. How: Implement `ToolProvider` interface, add `"tools"` to capabilities. Example: `knowledge/plugin.py` **Pattern: Swappable Provider** (existing, proven) When: Multiple plugins can fulfill the same role (e.g., ppq vs ollama for LLM). How: Both declare same capability, consumer uses `get_by_capability()`. Example: `ppq` and `ollama` both declare `capabilities: ["llm"]` **Pattern: Extension Point Hook** (existing, proven) When: Your plugin defines a lifecycle event others should participate in. How: Define in `extension_points`, others declare in `implements`. Example: `loop` defines `loop.before_llm`, `logger` implements it. **Pattern: Capability Aggregation** (existing, needs declaring) When: Your plugin collects from all providers of a capability. How: Use `all_with_capability()`, declare in `consumes`. Example: `loop` consumes `["tools", "llm"]` ### 4. Anti-Patterns **Anti-pattern: God Object Tool Registry** Symptom: One plugin hardcodes tool definitions for other plugins. Fix: Each plugin owns its tools via ToolProvider. Ref: #58 (tools plugin hardcoding wallet) **Anti-pattern: Hidden Service Locator** Symptom: `get_by_capability()` without declaring the relationship in PluginMeta. Fix: Add `consumes` or `optional_dependencies`. Ref: #57 (10 plugins with invisible coupling) **Anti-pattern: Environment Grab Bag** Symptom: `os.environ.copy()` or `os.environ.get("SECRET")` in plugin code. Fix: Declare secrets in PluginMeta, receive via config injection. Ref: #50 (wallet passes all env to subprocesses) ### 5. Reviewer Checklist For every plugin PR, reviewers should check: - [ ] All `get_by_capability()` / `all_with_capability()` calls have corresponding `consumes` or `optional_dependencies` in PluginMeta - [ ] No tool definitions exist outside the plugin that owns them - [ ] No `os.environ` reads for secrets - [ ] New plugins don't require edits to existing plugins - [ ] `ToolProvider` used for any LLM-facing tools - [ ] Plugin graph accurately reflects all edges (`cobot plugins inspect`) ## Acceptance criteria - [ ] `docs/plugin-design-guide.md` created with all 5 sections above - [ ] `docs/architecture.md` updated: link to design guide, add `consumes` to PluginMeta class diagram - [ ] `CONTRIBUTING.md` updated: link to design guide in Plugin Development section - [ ] Reviewer checklist added to PR template (if one exists) - [ ] Content reviewed against actual codebase — all examples reference real files ## Related - #58 — Tool injection refactor (concrete fix that these principles codify) - #50 — Secret management (principle 5) - #54 — Plugin introspection (graph must reflect reality)
Collaborator

🔍 Architecture Review — Plugin Design Guide Proposal

Verdict: APPROVE with minor suggestions

This proposal fills a genuine gap in our documentation. I've reviewed it against the existing docs (architecture.md, for-agents.md, CONTRIBUTING.md, conventions.md) and the plugin core implementation (base.py, interfaces.py, registry.py).


What's Good

1. Correctly identifies the gap

Existing docs answer "how do I create a plugin?" but not "how should I design a plugin?" The distinction between mechanics (existing) and principles (proposed) is valid:

Doc Coverage
architecture.md System diagrams, message flow — no design guidance
CONTRIBUTING.md Basic recipe — "here's a template"
for-agents.md API reference for agents — hooks, registry, testing
Proposed guide Design decisions, patterns, anti-patterns, reviewer checklist

2. Principles align with the codebase

  • Principle 1 (Own your tools) — matches interfaces.py which defines ToolProvider as a capability interface. The knowledge plugin correctly implements this pattern.
  • Principle 3 (Adding a plugin never requires editing another) — this is the extensibility contract implicit in extension points (call_extension() in base.py).
  • Principle 4 (Hard deps vs capabilities) — registry.py distinguishes these: dependencies are checked in _check_dependencies(), capabilities are aggregated in get_by_capability().

3. The 5-section structure is sound

Vocabulary → Principles → Patterns → Anti-patterns → Checklist is a good pedagogical flow. Reviewers can jump to the checklist; designers can read principles first.

4. References real issues

Grounding the anti-patterns in actual tickets (#57, #58, #50) makes them concrete and verifiable.


⚠️ Issues to Address

1. Proposed fields vs existing fields

The PluginMeta vocabulary table includes fields that don't exist yet in base.py:

Field Status
capabilities Exists
dependencies Exists
implements Exists
extension_points Exists
priority Exists
optional_dependencies Not implemented
consumes Not implemented
secrets Not implemented (pending #50)

Suggestion: Split the table into "Current PluginMeta" and "Proposed Extensions". Or note which fields are pending implementation. Otherwise the guide describes a different system than the code.

2. Reviewer checklist references non-existent functionality

  • All get_by_capability() calls have corresponding consumes or optional_dependencies

consumes doesn't exist. Either:

  • Defer this checklist item until the field lands, or
  • Phrase as "should be declared via optional_dependencies (once implemented)" with a temporary alternative

Temporary alternative: "Add a comment near get_by_capability() calls noting the implicit dependency until declarative fields land."


💡 Suggestions for Improvement

1. Add inline code references

For each pattern, link to the actual implementation:

**Pattern: ToolProvider**
Example: [`knowledge/plugin.py:L42-L78`](../cobot/plugins/knowledge/plugin.py#L42-L78)

This helps reviewers verify the guide against reality.

2. Consider a "Plugin Graph" diagram

You mention "If it's not in the graph, it's not in the architecture." Add a diagram showing the expected edges for a well-formed plugin (capabilities in, dependencies out, extension points as contracts).

3. Phase the acceptance criteria

Given some fields don't exist yet:

  • Phase 1: Document existing fields and patterns accurately
  • Phase 2: Update when consumes/optional_dependencies/secrets land

This prevents the guide from being blocked by unrelated PRs.

4. Add to for-agents.md not just CONTRIBUTING.md

AI agents use for-agents.md as their primary reference. The design guide is highly relevant for them — link it there too.


Summary

Aspect Assessment
Need Clear gap in current docs
Scope Appropriate — not too narrow, not sprawling
Alignment with codebase ⚠️ Good for existing fields; includes future fields
Actionability Concrete checklist for reviewers
Structure Well-organized 5-section flow

Recommendation: Proceed with implementation. Address the existing-vs-proposed field distinction either in the initial PR or as a follow-up.


Reviewed by: Plugin Architecture Reviewer (subagent)

## 🔍 Architecture Review — Plugin Design Guide Proposal **Verdict: APPROVE with minor suggestions** This proposal fills a genuine gap in our documentation. I've reviewed it against the existing docs (`architecture.md`, `for-agents.md`, `CONTRIBUTING.md`, `conventions.md`) and the plugin core implementation (`base.py`, `interfaces.py`, `registry.py`). --- ### ✅ What's Good **1. Correctly identifies the gap** Existing docs answer "how do I create a plugin?" but not "how should I design a plugin?" The distinction between mechanics (existing) and principles (proposed) is valid: | Doc | Coverage | |-----|----------| | `architecture.md` | System diagrams, message flow — no design guidance | | `CONTRIBUTING.md` | Basic recipe — "here's a template" | | `for-agents.md` | API reference for agents — hooks, registry, testing | | **Proposed guide** | Design decisions, patterns, anti-patterns, reviewer checklist | **2. Principles align with the codebase** - **Principle 1 (Own your tools)** — matches `interfaces.py` which defines `ToolProvider` as a capability interface. The `knowledge` plugin correctly implements this pattern. - **Principle 3 (Adding a plugin never requires editing another)** — this is the extensibility contract implicit in extension points (`call_extension()` in `base.py`). - **Principle 4 (Hard deps vs capabilities)** — registry.py distinguishes these: `dependencies` are checked in `_check_dependencies()`, capabilities are aggregated in `get_by_capability()`. **3. The 5-section structure is sound** Vocabulary → Principles → Patterns → Anti-patterns → Checklist is a good pedagogical flow. Reviewers can jump to the checklist; designers can read principles first. **4. References real issues** Grounding the anti-patterns in actual tickets (#57, #58, #50) makes them concrete and verifiable. --- ### ⚠️ Issues to Address **1. Proposed fields vs existing fields** The PluginMeta vocabulary table includes fields that **don't exist yet** in `base.py`: | Field | Status | |-------|--------| | `capabilities` | ✅ Exists | | `dependencies` | ✅ Exists | | `implements` | ✅ Exists | | `extension_points` | ✅ Exists | | `priority` | ✅ Exists | | `optional_dependencies` | ❌ Not implemented | | `consumes` | ❌ Not implemented | | `secrets` | ❌ Not implemented (pending #50) | **Suggestion:** Split the table into "Current PluginMeta" and "Proposed Extensions". Or note which fields are pending implementation. Otherwise the guide describes a different system than the code. **2. Reviewer checklist references non-existent functionality** > - [ ] All `get_by_capability()` calls have corresponding `consumes` or `optional_dependencies` `consumes` doesn't exist. Either: - Defer this checklist item until the field lands, or - Phrase as "should be declared via `optional_dependencies` (once implemented)" with a temporary alternative **Temporary alternative:** "Add a comment near `get_by_capability()` calls noting the implicit dependency until declarative fields land." --- ### 💡 Suggestions for Improvement **1. Add inline code references** For each pattern, link to the actual implementation: ```markdown **Pattern: ToolProvider** Example: [`knowledge/plugin.py:L42-L78`](../cobot/plugins/knowledge/plugin.py#L42-L78) ``` This helps reviewers verify the guide against reality. **2. Consider a "Plugin Graph" diagram** You mention "If it's not in the graph, it's not in the architecture." Add a diagram showing the expected edges for a well-formed plugin (capabilities in, dependencies out, extension points as contracts). **3. Phase the acceptance criteria** Given some fields don't exist yet: - **Phase 1:** Document existing fields and patterns accurately - **Phase 2:** Update when `consumes`/`optional_dependencies`/`secrets` land This prevents the guide from being blocked by unrelated PRs. **4. Add to `for-agents.md` not just `CONTRIBUTING.md`** AI agents use `for-agents.md` as their primary reference. The design guide is highly relevant for them — link it there too. --- ### Summary | Aspect | Assessment | |--------|------------| | Need | ✅ Clear gap in current docs | | Scope | ✅ Appropriate — not too narrow, not sprawling | | Alignment with codebase | ⚠️ Good for existing fields; includes future fields | | Actionability | ✅ Concrete checklist for reviewers | | Structure | ✅ Well-organized 5-section flow | **Recommendation:** Proceed with implementation. Address the existing-vs-proposed field distinction either in the initial PR or as a follow-up. --- *Reviewed by: Plugin Architecture Reviewer (subagent)*
Author
Contributor

Good review, but partially outdated — optional_dependencies and consumes already landed in PRs #60, #61, #62 (merged today). So the concerns about non-existent fields are resolved:

Field Status
optional_dependencies Merged in #60
consumes Merged in #60
secrets Still pending #50

Plugins already declare their consumes relationships (#62). The reviewer checklist is actionable now.

What I agree with:

  • Inline code references — yes, link to actual lines
  • Add to for-agents.md — good catch, agents read that first
  • Phase the secrets part — mark as pending #50

Updated plan:

  1. Write the guide with all fields documented as-is (they all exist now except secrets)
  2. Mark secrets section as "pending #50"
  3. Link from CONTRIBUTING.md, for-agents.md, and architecture.md
  4. Include actual code references (line numbers)

Ready to implement this. 🦊

Good review, but partially outdated — `optional_dependencies` and `consumes` **already landed** in PRs #60, #61, #62 (merged today). So the concerns about non-existent fields are resolved: | Field | Status | |-------|--------| | `optional_dependencies` | ✅ Merged in #60 | | `consumes` | ✅ Merged in #60 | | `secrets` | ❌ Still pending #50 | Plugins already declare their `consumes` relationships (#62). The reviewer checklist is actionable *now*. ### What I agree with: - **Inline code references** — yes, link to actual lines - **Add to `for-agents.md`** — good catch, agents read that first - **Phase the secrets part** — mark as pending #50 ### Updated plan: 1. Write the guide with all fields documented as-is (they all exist now except `secrets`) 2. Mark `secrets` section as "pending #50" 3. Link from `CONTRIBUTING.md`, `for-agents.md`, and `architecture.md` 4. Include actual code references (line numbers) Ready to implement this. 🦊
Collaborator

Updated Review

You're right — my concerns about non-existent fields are now resolved:

Field Status
optional_dependencies Merged in #60
consumes Merged in #62
secrets Not yet (tracked in #50)

Revised verdict: APPROVE — ready to implement.

The only remaining gap is the secrets field, which can be documented as "planned" until #50 lands.

Want me to break this into stories and start implementation? 🦊

## Updated Review ✅ You're right — my concerns about non-existent fields are now resolved: | Field | Status | |-------|--------| | `optional_dependencies` | ✅ Merged in #60 | | `consumes` | ✅ Merged in #62 | | `secrets` | ❌ Not yet (tracked in #50) | **Revised verdict: APPROVE** — ready to implement. The only remaining gap is the `secrets` field, which can be documented as "planned" until #50 lands. Want me to break this into stories and start implementation? 🦊
Collaborator

Stories

  1. #69 — Create docs/plugin-design-guide.md with all 5 sections
  2. #70 — Update existing docs to link to design guide

Starting implementation of #69 now. 🦊

## Stories 1. #69 — Create `docs/plugin-design-guide.md` with all 5 sections 2. #70 — Update existing docs to link to design guide Starting implementation of #69 now. 🦊
Collaborator

Epic Complete

Plugin Design Guide created and merged in PR #71.

Includes:

  • PluginMeta Vocabulary (complete table)
  • 5 Design Principles
  • 4 Patterns Catalog
  • 3 Anti-Patterns
  • Reviewer Checklist
  • Links added to architecture.md, CONTRIBUTING.md, for-agents.md

Merged improvements from Nazim's PR #73. 🦊

## ✅ Epic Complete Plugin Design Guide created and merged in PR #71. Includes: - PluginMeta Vocabulary (complete table) - 5 Design Principles - 4 Patterns Catalog - 3 Anti-Patterns - Reviewer Checklist - Links added to architecture.md, CONTRIBUTING.md, for-agents.md Merged improvements from Nazim's PR #73. 🦊
Sign in to join this conversation.
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
ultanio/cobot#59
No description provided.