dig's goal is to be the pi.dev of KB management: a small, sharp core with a rich ecosystem around it. Where a company needs to store blobs elsewhere, back up on every change, parse a proprietary format, or add a verb — that's an extension, not a fork.
pi.dev's mechanism (in-process TypeScript modules) does not port to a Go single binary — native Go plugins (
.so) are version-locked, Linux/macOS-only, and unsandboxed. dig keeps pi.dev's spirit (tiny core + ecosystem, manifest-described packages, a registry) with a Go-appropriate, two-axis design: typed extension points × tiered transports.
WHAT you can extend (typed seams) HOW it plugs in (transport tiers)
───────────────────────────────── ──────────────────────────────────
StorageBackend where blobs live T0 declarative — TOML + exec/webhook (no code)
EventSink react to changes T1 PATH subcommand dig-<name> (any language)
Extractor read content (OCR/parse) T2 gRPC subprocess (hashicorp/go-plugin)
Matcher custom file matching T3 WASM via wazero (sandboxed, cap-gated, pure-Go)
Action new workflow step
Command new `dig <verb>`
IndexBackend where the index lives
LLMProvider model endpoint (OpenAI-compat — already an interface)
You pick the point by what you're adding and the tier by how much trust/robustness it needs. The same StorageBackend interface can be satisfied by a webhook (T0), a Go binary over gRPC (T2), or a WASM module (T3).
Each is a small Go interface in the core. First-party implementations are compiled in; third-party ones arrive via a transport.
| Point | Interface (sketch) | Answers |
|---|---|---|
| StorageBackend | Put(hash, reader) · Get(hash) · Has(hash) · Delete(hash) |
"save data elsewhere" — S3, GCS, Azure, NAS, IPFS, a company blob store |
| EventSink | OnChangeset(event) error |
"backup system", notify, audit log, webhook, mirror-to-cloud, SIEM |
| Extractor | Extract(file) → fields/text |
new file types, OCR engines, proprietary parsers |
| Matcher | Match(file, args) → bool |
custom selection logic a rule can call |
| Action | Apply(ctx, files) → ops |
new workflow step ({ encrypt = … }, { transcode = … }) |
| Command | Run(args) → exit |
brand-new top-level verb dig <name> |
| IndexBackend | Index(manifest) · Query(q) |
swap FTS5 for an external search/vector engine |
| LLMProvider | OpenAI-compatible HTTP | already pluggable via [llm] base_url |
Design rule: a company's two stated needs — store elsewhere and back up — are one interface each (StorageBackend, EventSink), not a bespoke plugin. Most real needs collapse onto these eight seams; resist a generic "do-anything" plugin API.
Ordered by friction/trust. Build them lazily — most extensions never need more than T0/T1.
A rule/workflow step references an external command or webhook in config. Covers the long tail of "on change, do X."
[[event_sink]]
name = "offsite-backup"
on = "changeset.committed"
exec = "restic backup {changed_paths}" # or url = "https://hooks.internal/dig"- Isolation: OS process / network. Trust: you wrote the command. Use for: backups, notifications, mirrors.
execsinks are gated: they run a shell command, so dig only fires them whenDIG_ALLOW_EXEC_SINKS=1is set (default-deny code execution).urlwebhook sinks need no gate. Sinks observe — a sink failure warns, it never rolls back the changeset. (Shipped — seeSECURITY.md.)
Any executable named dig-<name> on $PATH becomes dig <name>. Zero protocol, any language.
- dig passes context via env +
--jsonon stdin; the subcommand emits a changeset proposal on stdout. - Isolation: separate process. Trust: installed by user. Use for: custom verbs, glue, prototypes.
The Terraform/Vault provider model: the extension is a long-lived binary; dig talks gRPC over a handshake. Typed, versioned, robust; a crash can't take down dig.
- Isolation: subprocess, can't corrupt host memory. Trust: signed/declared. Use for: production
StorageBackend/IndexBackend/ heavyExtractor.
A capability-sandboxed module run by a pure-Go runtime (wazero — keeps the cgo-free promise). No filesystem/network unless dig grants it.
- Isolation: strongest; deny-by-default capabilities. Trust: untrusted/community. Use for: third-party extractors/matchers from a public registry.
| Tier | Isolation | Lang | Single-binary preserved | Build when |
|---|---|---|---|---|
| T0 declarative | process/net | any | yes | day one |
| T1 PATH subcommand | process | any | yes (separate exe) | day one (free) |
| T2 gRPC subprocess | subprocess | Go + gRPC langs | host stays one binary | first real out-of-tree backend |
| T3 WASM | sandbox (cap-gated) | many → wasm | yes (wazero embedded) | untrusted third-party code is real |
Untrusted extensions are guilty until granted. Every non-first-party extension declares the capabilities it needs; dig enforces them and the user approves on install.
capabilities: [ storage:write, net:hooks.internal, read:finance/** ]
- Default deny: no FS, no net, no exec unless declared (enforced hard at T3, by convention/audit at T0–T2).
- Scoped to KB: an extension on KB "work" can't touch KB "research".
- Through the same spine: an extension can only ever propose a changeset. It never writes files directly — so every extension action is dry-run-able, journaled, and
undo-able like everything else. This is the safety guarantee that makes a plugin ecosystem tolerable. - Signing: registry packages are checksummed; signature verification gates install for T2/T3.
Every extension (above T0) ships a manifest — pi.dev-style, describing what it is and what it may do.
# dig-ext.toml
name = "s3-store"
version = "0.2.0"
provides = "StorageBackend" # which seam
transport = "grpc" # t0 | path | grpc | wasm
entry = "./dig-s3-store" # binary / wasm / command
capabilities = ["net:s3.amazonaws.com", "storage:write"]
config_schema = "schema.json" # validated at installdig ext list # installed, per KB + machine-wide
dig ext add github.com/acme/dig-s3-store # from git
dig ext add oci://registry/dig-backup:1.0 # from a registry
dig ext info s3-store # manifest, capabilities, signature
dig ext enable s3-store --kb work # grant + activate for a KB
dig ext disable s3-store --kb work
dig ext remove s3-store- Extensions install per-machine, are enabled per-KB (capabilities granted at enable time).
- Registry distribution mirrors pi.dev: git or an OCI/registry reference; a public catalog later.
The spine cannot be swapped, or the safety guarantees evaporate:
- content-addressed store semantics (hashing, manifests, journal) — backends plug in for where bytes live, not how history works
- the changeset state machine + merge/escalation
--dry-run/undo- the policy/rules/workflow evaluator
Extensions extend the edges (storage, events, extraction, matching, verbs, index, model) — never the spine.
These X-phases are extension-specific sub-tracks of the core roadmap in the README: X0 lands with core P0 (the seams are defined as the core is built — first-party backends are the interfaces), and X1–X4 are the public-transport work grouped under README P7.
- X0 — seams (with P0): the Go interfaces (
StorageBackend,EventSink,IndexBackend, …) are defined and first-party impls are compiled in (local FS store, FTS5 + vector index). Shipped. - [~] X1 — T0 + T1: T0 declarative event sinks (webhook +
DIG_ALLOW_EXEC_SINKS-gated exec) are shipped; T1 PATH subcommands (dig-<name>) are still to come. - X2 — registry:
dig extcommands + manifest + install-from-git. - X3 — T2 gRPC: first robust out-of-tree backend (e.g. a company blob store).
- X4 — T3 WASM + signing: sandboxed third-party extensions; public catalog.
The two motivating use cases (store elsewhere, backup) land at X1 — no gRPC/WASM needed. The heavy machinery is built only when the ecosystem demands it.
- pi.dev packages — the tiny-core-plus-ecosystem model dig mirrors
- hashicorp/go-plugin — gRPC subprocess plugins (Terraform/Vault)
- Extism / wazero — WASM plugin runtime (pure-Go host)
- git custom subcommands — PATH-based extension precedent
- gocloud.dev/blob — first-party
StorageBackendimplementations