Plugin Protocol
September 21, 2026 · View on GitHub
READ_ONLY_NO_ORDER_NO_CANCEL_NO_TRADE
SmartMoney-Cub is built so that "everything is a plugin" without giving plugins the ability to trade. The harness publishes a stable protocol, a reference plugin, and a curated catalog. It does not bundle AKShare, TradingAgents, Qlib, vectorbt, QuantStats, Backtrader, vn.py, or ZVT.
What automatic integration means here
A plugin that a user installs is found, validated, loaded, and injected automatically. There is no "edit the core to add a data source" step.
| Step | Automatic? | Notes |
|---|---|---|
| Discovery | Yes | Python entry points and explicit --plugin-dir paths |
| Validation | Yes | Manifest schema, safety declaration, API range, license field |
| Dependency resolution | Yes | inject; missing hard dependencies become PENDING |
| Activation | Yes | Gated by profile permissions and health check |
| Execution | Yes | Every result is wrapped in an Evidence Envelope |
| Installation | No | User-initiated; workbench can install into a dedicated venv upon explicit per-item confirmation |
| Downloading | No | Only via verified curated catalog whitelist on explicit confirmation; never silent background fetching |
| Enabling network or a model | No | Requires an explicit profile, credentials confirmation, or flag |
Layer model
The design follows the same three-layer separation that DeepSeek Harness uses: definition, provider, consumer.
| Layer | Responsibility | May depend on |
|---|---|---|
| Service definition | Stable request and result types for one capability | Nothing |
| Provider | One implementation of a capability | Its own project only |
| Consumer | Uses a capability by name | The definition only |
Because a consumer never imports a provider, replacing a provider requires no change to the consumer. That is what makes an external project swappable.
from smartmoney_cub_harness.plugins import BaseConsumer, CapabilityName
class ReviewDashboard(BaseConsumer):
required_services = (CapabilityName.TRADE_IMPORT,)
optional_services = (CapabilityName.MARKET_CONTEXT,)
def render(self, request):
return self.call(CapabilityName.TRADE_IMPORT, request)
Capabilities
| Capability | Purpose |
|---|---|
trade_import | Normalize broker or 同花顺 fills into a position ledger |
market_context | Read-only regime or sentiment context |
reviewer | Produce review observations |
challenger | Produce counter-arguments and rule candidates |
evaluator | Evaluate a candidate against point-in-time samples |
replay | Reconstruct a frozen decision context |
report_renderer | Render local artifacts |
memory | Store portable text memory |
llm_provider | Optional external model access |
agent_bridge | Bridge a local external agent as review evidence |
There is deliberately no order, cancel, account, or execution
capability. A manifest that declares one is rejected at load time.
Manifest
Every plugin ships a plugin.json. The reference file is
examples/toy_plugin/plugin.json.
The machine-readable schema is
schemas/plugin-manifest.schema.json.
| Field | Required | Meaning |
|---|---|---|
schema | Yes | smartmoney_cub_plugin_manifest.v1 |
plugin_id | Yes | Stable identifier; duplicates are rejected |
name, version | Yes | Identity and version recorded in evidence |
source_repo | Yes | Upstream project |
source_commit / source_tag | Recommended | Exact provenance |
license | Yes | Upstream license |
kind | Yes | entry-point, local-path, subprocess, or companion |
trust_level | Yes | core, review-only, data-network, untrusted-external |
api_range | Yes | Compatibility range, for example >=1,<2 |
capabilities | Yes | Non-empty list of capability names |
data_time_semantics | Yes | point_in_time, historical_export, live_fetch, derived_static |
required_services / optional_services | No | Dependency seams |
network_required | No | Defaults to false |
credential_requirements | No | Names only; values stay in the user's environment |
supported_markets | No | For example CN-A |
safety | Yes | Must be READ_ONLY_NO_ORDER_NO_CANCEL_NO_TRADE |
Lifecycle and Status Vocabulary
AVAILABLE -> [PERMISSIONS_CONFIRMED] -> INSTALLED (in dedicated venv/sources) -> ENABLED/DISABLED -> HEALTH_CHECKED
-> ACTIVE -> EXECUTED -> EVIDENCE_WRAPPED -> REVIEWED
-> UNINSTALLED / REVOKED
PENDING (a required service is missing)
FAILED / ERROR (a health probe, installation, or execution raised)
BLOCKED (high execution risk or the active profile does not permit permissions)
Market States
The workbench and catalog contract define five explicit market states:
| State | Meaning |
|---|---|
AVAILABLE | The plugin is cataloged and available for installation, but not yet downloaded or installed. |
INSTALLED | The plugin package has been fetched into the dedicated venv (or cloned into sources/), passes health check, but is currently inactive. |
ENABLED | The plugin is active and available for invocation in workflows. |
DISABLED | The plugin is installed but intentionally disabled by the user or profile. |
ERROR | A health probe, installation step, or execution failed, or the entry is in an invalid state. |
Activation registers providers as reversible effects. Deactivation runs those disposers in reverse order, so no consumer keeps a reference to a provider that is no longer loaded. Removing a plugin revokes the entry while keeping its audit trail.
Isolation and permission honesty
Third-party projects default to a report-only subprocess provider. A subprocess plugin receives one JSON request on stdin and returns one JSON object on stdout.
Declared permissions are a statement, not a sandbox attestation. Doctor output and
every envelope therefore report enforcement: declarative and verified: false.
Place untrusted code in an operating-system or container sandbox before running it.
Evidence Envelope
Every execution returns a wrapped envelope rather than a bare result:
- plugin id, version, and source reference;
- input and output SHA-256;
- decision time and available time;
- data source and data quality;
- whether network or a model was used;
- declared permission state;
- the normalized
result_kind, which separates facts, statistics, model opinions, and user records; champion_mutated: falseandcore_rules_mutated: false.
An output whose available_at is after decision_time raises instead of being
recorded. That single check prevents most look-ahead mistakes.
Command line
smcub plugin list --plugin-dir examples/toy_plugin
smcub plugin inspect examples/toy_plugin/plugin.json
smcub plugin doctor --plugin-dir examples/toy_plugin
smcub plugin install ./my-plugin # registers a local path, never downloads
smcub plugin enable toy.review-tagger --plugin-dir examples/toy_plugin
smcub plugin run toy.review-tagger \
--request request.json \
--decision-time 2026-09-10T15:00:00+08:00 \
--available-at 2026-09-10T14:00:00+08:00 \
--workspace-db state/workspace/review.db --case-id CASE-1
smcub plugin logs toy.review-tagger
smcub plugin disable toy.review-tagger
smcub plugin remove toy.review-tagger
smcub plugin catalog
smcub profile show default-offline
smcub profile dump --output profiles.json
smcub profile reload --plugin-dir examples/toy_plugin
Installation Channels and Workbench Wizard
The harness provides two installation avenues:
- CLI Local Registration:
smcub plugin install <local-dir>registers an existing local directory or manifest path. - Workbench Installation Wizard (Dedicated venv & Whitelist):
The review workbench provides an interactive, human-gated installation flow for curated catalog plugins:
- Catalog Whitelist Enforcement: Only entries present in the curated catalog whitelist (
catalog_index()) can be installed. Arbitrary URLs or unauthorized packages are strictly rejected. - Absolute Execution Ban on High Risk: Catalog entries marked with
execution_risk: "high"(such as vn.py, which contains order placement and account manipulation capabilities) are never installed under any circumstance. The installer immediately refuses them. - Explicit Permission Confirmation: Installation cannot proceed without explicit human consent to the
READ_ONLY_NO_ORDER_NO_CANCEL_NO_TRADEboundary. - Dedicated Virtual Environment (
.plugins/venv): PyPI-based plugins are installed into an isolated dedicated virtual environment, avoiding pollution of the core runtime. - Dedicated Sources Directory (
.plugins/sources): Git-based plugins are cloned into an isolated sources directory with health probes verified. - Health Checks & Probes: Immediately following download/install, a non-mutating health check (
probe) verifies that the declared module can be imported cleanly. If the probe fails, the installer reportsERROR, rolls back changes, and surfaces the failure. - Clean Uninstallation: Uninstallation cleanly removes packages from the dedicated venv or deletes cloned directories while preserving audit logs.
- Catalog Whitelist Enforcement: Only entries present in the curated catalog whitelist (
Pass --workspace-db to plugin run to persist the wrapped envelope into the review
workspace, optionally linked to a case with --case-id.
Profiles
| Profile | Network | External model | Contents |
|---|---|---|---|
default-offline | No | No | Offline core only |
a-share-review | No | No | Offline core plus A-share review helpers |
research | No | No | Adds disabled evaluation and replay slots |
ai-optional | Yes | Yes | Adds disabled LLM and agent bridge slots |
Composition is ordered bundles plus user patches. Because entries have stable ids, a patch keeps applying to the same logical slot even when providers change.
Curated catalog
smcub plugin catalog lists external projects with three integration levels:
companion— documentation only; nothing is executed.adapter— wraps external output into an Evidence Envelope, preferably as a report-only subprocess.runtime-plugin— a manifest, tests, permission declarations, safety docs, and a health check exist.
Projects with high execution risk, such as vn.py, stay at companion level and are
listed with no capabilities. A catalog entry is never a bundled dependency.
Schemas
| Schema | Purpose |
|---|---|
| plugin-manifest.schema.json | Plugin declaration and the capability names it may not use |
| plugin-evidence-envelope.schema.json | Wire shape of wrapped plugin output |
Writing a plugin
- Copy examples/toy_plugin as a starting point.
- Describe the plugin in
plugin.json. Keepnetwork_requiredfalse unless the plugin genuinely must reach the network. - Implement the provider. For a subprocess plugin, read JSON from stdin and write JSON to stdout.
- Validate with
smcub plugin inspect plugin.json. - Run it with
smcub plugin runand confirm the envelope has no error and reports the expectedresult_kind. - Add tests covering success, a missing dependency, a future-data refusal, and a failure that must stay visible.
What plugins must not do
- Place, cancel, or simulate orders; modify accounts; automate a broker.
- Present a model opinion as a fact, a statistic, or a signal.
- Write champion rules. A plugin may only propose a candidate.
- Read credentials, cookies, or account identifiers, or write them into artifacts.
- Require network access or an external model without an explicit user opt-in.
- Claim sandbox verification that the harness has not performed.