Create Plugin Design Guide: codify architecture principles for reviewers and designers #59
Labels
No labels
Compat/Breaking
Kind/Bug
Kind/Competitor
Kind/Documentation
Kind/Enhancement
Kind/Epic
Kind/Feature
Kind/Security
Kind/Story
Kind/Testing
Priority
Critical
Priority
High
Priority
Low
Priority
Medium
Reviewed
Confirmed
Reviewed
Duplicate
Reviewed
Invalid
Reviewed
Won't Fix
Scope/Core
Scope/Cross-Plugin
Scope/Plugin-System
Scope/Single-Plugin
Status
Abandoned
Status
Blocked
Status
Need More Info
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
ultanio/cobot#59
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
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:
capabilitiesvsdependenciesvsextension_pointsget_by_capability()is correct vs when to declare a hard dependencyToolProviderpattern and how to add tools to the agentWe 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.mdA single authoritative document covering:
1. PluginMeta Vocabulary
Complete reference for every field and when to use it:
capabilities["wallet", "tools"]dependencies["config", "persistence"]optional_dependencies["llm"]for securityconsumesall_with_capability()["tools", "llm"]for loopimplements{"context.system_prompt": "get_soul"}extension_points["loop.before_llm"]priority2. Design Principles
Principle 1: Own your tools
If your plugin provides tools to the LLM agent, implement
ToolProviderand declarecapabilities: ["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()orall_with_capability(), declare it in PluginMeta viaconsumesoroptional_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 configconsumes: ["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 declaredsecretsfield (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
ToolProviderinterface, add"tools"to capabilities.Example:
knowledge/plugin.pyPattern: 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:
ppqandollamaboth declarecapabilities: ["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 inimplements.Example:
loopdefinesloop.before_llm,loggerimplements it.Pattern: Capability Aggregation (existing, needs declaring)
When: Your plugin collects from all providers of a capability.
How: Use
all_with_capability(), declare inconsumes.Example:
loopconsumes["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
consumesoroptional_dependencies.Ref: #57 (10 plugins with invisible coupling)
Anti-pattern: Environment Grab Bag
Symptom:
os.environ.copy()oros.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:
get_by_capability()/all_with_capability()calls have correspondingconsumesoroptional_dependenciesin PluginMetaos.environreads for secretsToolProviderused for any LLM-facing toolscobot plugins inspect)Acceptance criteria
docs/plugin-design-guide.mdcreated with all 5 sections abovedocs/architecture.mdupdated: link to design guide, addconsumesto PluginMeta class diagramCONTRIBUTING.mdupdated: link to design guide in Plugin Development sectionRelated
🔍 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:
architecture.mdCONTRIBUTING.mdfor-agents.md2. Principles align with the codebase
interfaces.pywhich definesToolProvideras a capability interface. Theknowledgeplugin correctly implements this pattern.call_extension()inbase.py).dependenciesare checked in_check_dependencies(), capabilities are aggregated inget_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:capabilitiesdependenciesimplementsextension_pointspriorityoptional_dependenciesconsumessecretsSuggestion: 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
consumesdoesn't exist. Either:optional_dependencies(once implemented)" with a temporary alternativeTemporary 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:
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:
consumes/optional_dependencies/secretslandThis prevents the guide from being blocked by unrelated PRs.
4. Add to
for-agents.mdnot justCONTRIBUTING.mdAI agents use
for-agents.mdas their primary reference. The design guide is highly relevant for them — link it there too.Summary
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)
Good review, but partially outdated —
optional_dependenciesandconsumesalready landed in PRs #60, #61, #62 (merged today). So the concerns about non-existent fields are resolved:optional_dependenciesconsumessecretsPlugins already declare their
consumesrelationships (#62). The reviewer checklist is actionable now.What I agree with:
for-agents.md— good catch, agents read that firstUpdated plan:
secrets)secretssection as "pending #50"CONTRIBUTING.md,for-agents.md, andarchitecture.mdReady to implement this. 🦊
Updated Review ✅
You're right — my concerns about non-existent fields are now resolved:
optional_dependenciesconsumessecretsRevised verdict: APPROVE — ready to implement.
The only remaining gap is the
secretsfield, which can be documented as "planned" until #50 lands.Want me to break this into stories and start implementation? 🦊
Stories
docs/plugin-design-guide.mdwith all 5 sectionsStarting implementation of #69 now. 🦊
✅ Epic Complete
Plugin Design Guide created and merged in PR #71.
Includes:
Merged improvements from Nazim's PR #73. 🦊