Workflows & diagrams¶
Visual, at-a-glance references for the TOVIO plugin system. Prose explanations live on the linked pages — this page is a map.
How to read these
Rectangles are states or artifacts, rounded shapes are actions, diamonds are decisions, and dashed lines cross the sandbox boundary. Colors indicate trust zones: the developer's machine, the TOVIO core, the sandbox, the Forge, and the audit log.
1. The three-layer model at a glance¶
The plugin system separates what a plugin is from when it runs from how it executes. Core defines the contracts; runtime hosts execute.
flowchart LR
subgraph DEF["Definition"]
M["manifest.toml"]
A["Runtime artifact<br/>(plugin.wasm)"]
I["Integrity metadata<br/>(sha256 + publisher signature)"]
end
subgraph BIND["Binding"]
B1[".tovio/plugins/bindings.toml<br/>(tovio plugin bind)"]
B2["…the same file<br/>(tovio policy hook add)"]
B3["Forge-side required check<br/>(plugin:<id> attestation)"]
end
subgraph EXEC["Execution"]
E1["Event fires"]
E2["Sandbox invocation"]
E3["Structured result"]
end
DEF --> BIND --> EXEC
E3 --> AUD["PluginBlocked audit entry<br/>(on a block only)"]
classDef def fill:#eef,stroke:#557;
classDef bind fill:#efe,stroke:#575;
classDef exec fill:#fee,stroke:#755;
classDef aud fill:#ffe,stroke:#775;
class M,A,I def;
class B1,B2,B3 bind;
class E1,E2,E3 exec;
class AUD aud;
See Concepts for the prose version and Manifest reference for every field in the definition layer.
2. Install → verify → bind → run¶
The end-to-end journey a developer takes to put a plugin into service.
flowchart TD
S([Start]) --> INS["tovio plugin install <path or https URL>"]
INS --> PARSE{"Manifest valid?"}
PARSE -- no --> E1[["TVO-PLUGIN-001<br/>manifest invalid"]]
PARSE -- yes --> INTEG{"integrity"}
INTEG -->|local_dev| DEVOK{"local source?"}
DEVOK -- no --> E2[["TVO-PLUGIN-003<br/>integrity check failed"]]
DEVOK -- yes --> STORE["Store in .tovio/plugins/<id>/<br/>verification: local-development"]
INTEG -->|signed| INT{"artifact hashes<br/>to sha256?"}
INT -- no --> E2
INT -- yes --> SIG{"detached publisher<br/>signature present?"}
SIG -- no --> HASHONLY{"local source?"}
HASHONLY -- no --> E2
HASHONLY -- yes --> STORE2["Store<br/>verification: sha256-only"]
SIG -- yes --> TRUST{"signature verifies,<br/>publisher trusted,<br/>not revoked,<br/>id not pinned elsewhere?"}
TRUST -- no --> E2
TRUST -- yes --> STORE3["Store<br/>verification: publisher-signature+sha256"]
STORE --> LIST["tovio plugin list / show / verify"]
STORE2 --> LIST
STORE3 --> LIST
LIST --> BIND["tovio plugin bind …<br/>or tovio policy hook add …"]
BIND --> READY(["Bound & ready"])
READY --> EV{"Event fires?"}
EV -->|any bound event| RUN["Sandboxed execution"]
RUN --> RESULT{{"status: pass / warning /<br/>fail / error / skipped / unsupported"}}
classDef err fill:#fdd,stroke:#c33,color:#600;
class E1,E2 err;
"local source?" is the HTTPS rule in the other direction: a remote install has to reach STORE3 —
local_dev and hash-only packages are refused from a URL.
Referenced pages: Use a plugin, Bindings, CLI reference, Errors.
3. Where an event's bindings come from¶
The client reads exactly one binding file, and adds the on-by-default first-party slots that no user authored. A local binding is never a repository, organization, or Forge policy gate — Forge-side required checks are a separate mechanism (diagram 10).
flowchart TB
subgraph L2[" This repository — .tovio/, unsynced "]
R[".tovio/plugins/bindings.toml<br/>written by plugin bind and policy hook add<br/>scope = local"]
end
subgraph L3[" Built-in default slots "]
U["com.tovio.secret-scan on pre-snapshot<br/>enforcing + required<br/>unless secrets.scan = off"]
end
R --> COMPOSE{"Bindings that<br/>match this event"}
U --> COMPOSE
COMPOSE --> FILTER["Drop those whose path,<br/>branch, or actor filters miss"]
FILTER --> EFF([Effective binding set for event])
Every matching binding runs; there is no precedence contest, and the first one to block is the one the error reports. A weaker user binding for the same id and event does not suppress a default slot unless it offers the same universal enforcing guarantee — otherwise a narrow shadow-bind would silently switch enforced scanning off.
See Bindings for the path/branch/actor filters and the on-disk field reference.
4. Lifecycle events — where plugins fit¶
Four representative events, and where each sits in a developer's or Forge's normal flow. Most of the
lifecycle set fires today — post-*, sync, permission, lock, agent, and git-bridge events included;
Lifecycle events records firing-site availability event by event.
sequenceDiagram
autonumber
actor Dev as Developer
participant CLI as tovio-cli
participant Core as tovio-core
participant SB as Plugin sandbox
participant Forge
Dev->>CLI: edit files
CLI->>Core: auto-snapshot working copy
Core-->>SB: pre-snapshot(input)
SB-->>Core: PluginResult
Core->>CLI: allow / warn / block
Dev->>CLI: tovio land feature/deps --into main
CLI->>Core: prepare land
Core-->>SB: pre-land(input)
SB-->>Core: PluginResult
Core->>CLI: allow / block (fail-closed if required)
Dev->>CLI: tovio resolve --ai
CLI->>Core: resolve request
Core-->>SB: resolve-requested(input)
SB-->>Core: proposed Resolution
Core->>CLI: apply via normal resolution path
Dev->>Forge: create proposal
Forge->>Core: proposal-created event
Core-->>SB: proposal-created(input, metadata-only)
SB-->>Core: PluginResult
Core->>Forge: surface in proposal status
Full payload details: Lifecycle events.
5. The enforcing decision (fail-closed)¶
The rule that decides whether an operation proceeds when an enforcing plugin has spoken.
An advisory binding is the one branch that always allows. Everything below is the enforcing branch.
stateDiagram-v2
[*] --> Running
Running --> pass: status = pass
Running --> warning: status = warning
Running --> fail: status = fail
Running --> error: status = error
Running --> skipped: status = skipped
Running --> unsupported: status = unsupported
Running --> malformed: schema invalid<br/>(REQ-PLUGIN-052)
pass --> Allow
warning --> Allow: (surfaced but non-blocking)
fail --> Block: REQ-PLUGIN-054
malformed --> error
state ErrorCheck <<choice>>
error --> ErrorCheck
ErrorCheck --> Block: operation touches<br/>protected paths<br/>(REQ-PLUGIN-056)
ErrorCheck --> Block: required = true<br/>(REQ-PLUGIN-055)
ErrorCheck --> Allow: neither
state DeclinedCheck <<choice>>
skipped --> DeclinedCheck
unsupported --> DeclinedCheck
DeclinedCheck --> Block: required = true<br/>(REQ-PLUGIN-055)
DeclinedCheck --> Allow: required = false
Allow --> [*]
Block --> [*]
skipped and unsupported are well-formed "not applicable" verdicts rather than failures, so the
protected fail-closed rule does not reach them — only error blocks a protected operation a
non-required binding could otherwise wave through.
Prose: Concepts → fail-closed enforcement.
6. The capability intersection¶
The effective capability set is the intersection of four independent grants. If any layer withholds a capability, the plugin does not get it.
flowchart LR
M["Manifest<br/>requests"] --> X((∩))
B["Binding<br/>approves"] --> X
A["Actor<br/>authorization"] --> X
T["Agent token<br/>(if agent-run)"] --> X
X --> EFF["Effective capability set<br/>(what the sandbox actually grants)"]
EFF --> SB[/"Passed into PluginInput.capabilities"/]
classDef req fill:#eef,stroke:#557;
classDef eff fill:#efe,stroke:#575;
class M,B,A,T req;
class EFF,SB eff;
The full deny-by-default set and per-capability rules: Capabilities & security.
7. The sandbox boundary¶
Exactly what crosses in and out of the WASM/WASI sandbox for a single execution.
flowchart LR
subgraph HOST[" tovio-cli / tovio-forge / CI runner "]
IN["PluginInput JSON<br/>(api_version, event, mode,<br/>capabilities, redacted payload)"]
OUT["PluginResult JSON<br/>(status, findings,<br/>proposed_actions)"]
TIMER["Deadline (manifest timeout_ms)<br/>+ a fuel budget derived from it"]
end
subgraph SB[" WASM/WASI sandbox "]
CODE["Plugin code"]
end
IN -. stdin .-> CODE
CODE -. stdout .-> OUT
TIMER -. trap on exhaustion / kill on expiry .-> CODE
NOFS["✗ Filesystem<br/>(no path_* at all)"]:::deny
NONET["✗ Network<br/>(no sock_* at all;<br/>a granted network_access<br/>is refused at preflight,<br/>TVO-PLUGIN-012)"]:::deny
NOKEYS["✗ Key material<br/>(ever)"]:::deny
NOPT["✗ Protected plaintext<br/>(default)"]:::deny
NOENV["✗ Ambient env secrets<br/>(environ_* report empty)"]:::deny
NODOTVX["✗ .tovio/ internals"]:::deny
CODE -.- NOFS
CODE -.- NONET
CODE -.- NOKEYS
CODE -.- NOPT
CODE -.- NOENV
CODE -.- NODOTVX
classDef deny fill:#fdd,stroke:#c33,color:#600;
8. Blocked-land recovery flow¶
When a required plugin blocks tovio land, this is the user's decision tree.
flowchart TD
L["tovio land feature/deps --into main"] --> P["Required plugins run"]
P --> R{"Any status<br/>≠ pass/warning?"}
R -- no --> OK([✓ Landed])
R -- yes --> B[["✗ TVO-PLUGIN-009 / -010<br/>structured block, nothing changed"]]
B --> WHY{"Why?"}
WHY -->|fail with a finding| FIX["Fix the finding<br/>code + remediation"]
FIX --> L
WHY -->|error| DOC["Read the plugin's own summary<br/>then tovio plugin doctor"]
DOC --> LIFT
WHY -->|timeout TVO-PLUGIN-007| RETRY["Retry once,<br/>then raise timeout_ms"]
RETRY --> L
WHY -->|unavailable TVO-PLUGIN-010| REINST["Reinstall / update, or rebuild<br/>tovio with --features plugins-wasm"]
REINST --> L
WHY -->|integrity TVO-PLUGIN-003| STOP[["Do NOT work around it.<br/>Report to the publisher."]]
WHY -->|capability denied TVO-PLUGIN-005| ADJ["Adjust binding or manifest"]
ADJ --> L
LIFT["Owner lifts the LOCAL binding:<br/>tovio plugin unbind <id> --event pre-land<br/>or re-bind it --mode advisory"]
LIFT --> L
classDef stop fill:#fdd,stroke:#c33,color:#600;
class STOP,B stop;
There is no override flag on tovio land or tovio commit. A local enforcing binding is lifted by
dropping it or re-binding it advisory — and the block that preceded that is already a signed
PluginBlocked entry, so the sequence stays visible to anyone who verifies the chain. A Forge-side
required check is a forge-admin's change, not a client-side flag.
Full catalog: Errors. Lifting a binding: Use a plugin.
9. Audit write path (enforcing blocks)¶
An enforcing execution that blocks produces a tamper-evident audit record. A pass or a non-blocking warning writes nothing — the entry exists to record the block. The plugin never writes to the audit log directly; the host does, from hashes and capability names, never from plugin content.
sequenceDiagram
autonumber
participant Host as tovio-cli runner
participant SB as Sandbox
participant Audit as Signed audit log
participant Verify as tovio audit verify
Host->>SB: PluginInput (with the effective grant)
SB-->>Host: PluginResult
Host->>Host: decision(mode, required, status, touches_protected)
Host->>Host: Block ⇒ compute input_hash, output_hash
Host->>Audit: PluginBlocked {plugin_version, plugin_mode, plugin_status,<br/>plugin_target, plugin_finding_count, binding_id,<br/>input_hash, output_hash, plugin_sandbox_runtime,<br/>plugin_granted_capabilities, plugin_touches_protected}
Audit-->>Audit: Sign + chain to this identity's previous entry
Verify->>Audit: tovio audit verify
Audit-->>Verify: OK (or tamper-evident failure)
A Simple repo has no signing identity and so no chain: the block still gates the operation, but nothing is written.
10. Local, CI, and Forge parity¶
The same manifest + same pinned version + same input yields the same result across all three runtime hosts. Forge decides whether to trust a local result via attestation or server-side execution.
flowchart LR
subgraph DEV[" Developer laptop "]
LC["tovio-cli<br/>local sandbox"]
end
subgraph CI[" CI runner "]
CC["tovio-cli<br/>runner sandbox"]
end
subgraph FORGE[" Forge "]
FS["tovio-forge<br/>server-side sandbox"]
end
PLUG(["Same plugin @ pinned version<br/>Same input schema"]) --> LC
PLUG --> CC
PLUG --> FS
LC --> LR["Result (local)"]
CC --> CR["Result (CI, attestable)"]
FS --> FR["Result (server, trusted)"]
LR --> TRUST{"Trusted as required gate?"}
CR --> TRUST
FR --> TRUST
TRUST -- server-side --> YES([Accepted])
TRUST -- attestation-approved --> YES
TRUST -- local only --> NO([Advisory only])
11. Agents running plugins¶
When an agent triggers a plugin (via MCP), the agent's capability token further constrains the effective capability set — the intersection now has four inputs, and the token is one of them.
sequenceDiagram
autonumber
actor Human
participant CLI as tovio-cli
participant Agent as MCP agent
participant MCP as tovio-mcp-server (stdio)
participant Core as tovio-core
participant SB as Plugin sandbox
Human->>CLI: tovio agent new … (issue token)
Human-->>Agent: token
Agent->>MCP: call tool (e.g. propose_land)
MCP->>Core: validate token vs. request
Core->>Core: intersect: manifest ∩ binding ∩ actor ∩ token
Core-->>SB: PluginInput with narrowed capabilities
SB-->>Core: PluginResult
Core-->>MCP: structured status + findings
MCP-->>Agent: typed response (agent branches on code)
See Agents → capability tokens and MCP server.
12. tovio resolve --ai — resolver plugin flow¶
The conflict-resolver seam is the same plugin framework, in resolver mode.
flowchart TD
C["Conflict detected"] --> AI["tovio resolve --ai"]
AI --> BOUND{"Resolver plugin<br/>bound to<br/>resolve-requested?"}
BOUND -- no --> NORES[["Unchanged conflict<br/>+ no-resolver typed error<br/>(REQ-PLUGIN-079)"]]
BOUND -- yes --> RUN["Sandboxed execution"]
RUN --> PROP{"Proposed Resolution?"}
PROP -- no --> KEEP["Leave conflict for human"]
PROP -- yes --> ACCEPT{"Actor accepts?"}
ACCEPT -- no --> KEEP
ACCEPT -- yes --> APPLY["Apply via normal<br/>Resolution path<br/>(REQ-PLUGIN-080)"]
APPLY --> DONE([✓ Conflict resolved])
See Concepts → conflicts and everyday → resolve conflicts.
13. What a result's proposed actions do¶
A result's status is what gates (diagram 5), and that path is the same for every plugin type. What
differs is whether anything consumes the result's proposed_actions, and today exactly three kinds
are consumed.
flowchart LR
R[PluginResult] --> ST["status → the decision ladder<br/>same for every type"]
R --> PAS{"proposed_actions kind"}
PAS -->|protect-path| PP["Offer encrypt-in-place<br/>to the human<br/>never auto-applied"]
PAS -->|resolution| RS{"Is the plugin<br/>type = resolver?"}
RS -- no --> DROP["Ignored — a non-resolver<br/>cannot smuggle a resolution"]
RS -- yes --> APPLY["Apply via the normal<br/>Resolution path"]
PAS -->|transcript-turns| TT["Used to build the<br/>commit's session record<br/>an answer, not a gate"]
PAS -->|anything else| IGN["Recorded in the result,<br/>consumed by nothing"]
transform, policy-adapter, and notifier are valid manifest types and their capability requests are
validated, intersected, and audited like any other — but no host applies a transform, routes a
policy-adapter's verdict, or delivers a notifier's output yet. Declare one and it runs and reports; only
its status has an effect.
Where to go next¶
- Concepts — the model in words.
- Use a plugin — install → bind → run.
- Write a plugin — author guide.
- Capabilities & security — the deny-by-default model.
- Errors — recovery for every
TVO-PLUGIN-*code.
Last reviewed September 9, 2026
Suggest an improvement to this page Not for security reports — see disclosure