-
Notifications
You must be signed in to change notification settings - Fork 0
Adapters
Adapters are how Coggle knows what external tools exist and what they can do. Each adapter wraps a single tool — mv, ffmpeg, convert — and declares its capabilities in a self-describing way that the NLP pipeline can query at runtime.
An adapter is a TOML file with three top-level sections: the adapter identity, its domain definitions, and its capabilities.
adapter/
[adapter] — name and aliases
[[domains]] — what file types this adapter operates on
[[capabilities]]— what operations it can perform
[adapter]
name = "UNIX File Management"
aliases = ["unix", "filesystem", "fs"]aliases are the names a user can use to force this adapter explicitly — e.g. "move these files using unix".
A domain defines what kinds of files a capability operates on. Each adapter declares one or more domains, and each capability references exactly one by name.
[[domains]]
name = "video"
description = "Video files"
extensions = [".mp4", ".mkv", ".webm", ".mov"]
mimetypes = ["video/mp4", "video/x-matroska", "video/webm"]A domain must define exactly one of the following matching strategies:
| Field | Type | Description |
|---|---|---|
match |
"any" |
Wildcard — matches any file regardless of type |
extensions |
array<string> |
Matched against the TARGET file extension |
mimetypes |
array<string> |
Matched against the detected file MIME type |
extensions and mimetypes may be used together. match = "any" is mutually exclusive with both.
# Filesystem domain — applies to any file type
[[domains]]
name = "filesystem"
description = "Any file or directory regardless of type"
match = "any"Domain matching acts as a filter on the candidate capability set, not a hard selector. A match = "any" domain never eliminates candidates — intent triggers and slot fitting handle disambiguation when multiple adapters cover the same file type.
A capability is a single operation the adapter can perform. It maps to exactly one intent and defines the full parameter signature needed to construct the command.
[[capabilities]]
domain = "filesystem"
name = "move"
triggers = ["move", "put", "shift", "transfer", "relocate"]
description = "Move one or more files or directories to a destination"
destructive = true
command = { base = "mv", positional_order = ["source", "destination"], execution = "single" }
[capabilities.slots]
source = { category = "TARGET", type = "filepath", required = true, cardinality = "many", expansion = "inline", render = "positional", desc = "Files to move" }
destination = { category = "DESTINATION", type = "filepath", required = true, cardinality = 1, render = "positional", desc = "Location to move files into" }
no_clobber = { category = "ARGUMENT", type = "boolean", required = false, default = false, render = "flag", flag = "-n", desc = "Do not overwrite existing files" }
verbose = { category = "ARGUMENT", type = "boolean", required = false, default = false, render = "flag", flag = "-v", desc = "Print each file as it is moved" }| Field | Type | Required | Description |
|---|---|---|---|
domain |
string |
yes | Must match a domain name defined in [[domains]]
|
name |
string |
yes | Unique identifier within this adapter |
triggers |
array |
yes | Intent keywords that activate this capability |
description |
string |
yes | Used by the slot mapper for confidence scoring |
destructive |
boolean |
yes | If true, requires dry-run confirmation before execution |
command |
object |
yes | Describes how to build the shell invocation |
| Field | Type | Description |
|---|---|---|
base |
string |
The binary to invoke e.g. "mv", "ffmpeg"
|
positional_order |
array<string> |
Slot names rendered as positional args, in order |
execution |
"single" | "loop"
|
single = one invocation for all targets; loop = one invocation per target |
Slots are the parameters of a capability. Each slot is an inline table keyed by its name. Every capability must have at least one slot with category = "TARGET".
| Field | Type | Required | Description |
|---|---|---|---|
category |
TARGET | DESTINATION | CONSTRAINT | ARGUMENT
|
yes | The span category that fills this slot |
type |
see Slot Types | yes | Semantic type of the value |
required |
boolean |
yes | Hard-rejects the capability during selection if unfillable and no default is set |
desc |
string |
yes | Natural language description used for semantic slot matching |
render |
"positional" | "flag"
|
yes | How the slot is rendered in the command |
cardinality |
integer | "many"
|
no | Defaults to 1. Use "many" for unbounded inputs |
expansion |
"inline" | "loop"
|
if cardinality = "many"
|
How multiple values are passed to the tool |
flag |
string |
if render = "flag"
|
The flag string e.g. "-n", "--verbose"
|
default |
any | no | Only valid when required = false
|
keywords |
array<string> |
no | Fast-path synonyms for ARGUMENT slot matching |
values |
array<string> |
if type = "enum"
|
The set of valid values for enum slots |
| Category | Role |
|---|---|
TARGET |
The file(s) being operated on |
DESTINATION |
Where the result goes |
CONSTRAINT |
Filtering criteria — narrows which files the operation applies to |
ARGUMENT |
Operation modifiers — changes how the operation runs |
Slot types are broad enough to cover many domains without needing new types per adapter, but specific enough that each type encodes a distinct parsing behaviour and produces a distinct structure for the command builder.
| Type | Parsed as | Examples |
|---|---|---|
filepath |
Path or glob, resolved at runtime |
*.mp4, /home/user/file.txt
|
boolean |
Presence or absence of a flag |
true, false
|
integer |
Dimensionless whole number |
30, 2, 100
|
quantity |
Scalar number + unit |
10MB, 30fps, 2Mbps, 128kbps, 2x
|
dimensions |
Width × height compound value |
1920x1080, 1080p, 800x600
|
timestamp |
Time offset or point |
00:01:30, 90s, 1:30:00
|
enum |
Single select from a defined set |
h264, lanczos, nearest
|
string |
Free text, last resort | metadata title, output label |
quantity covers any slot that combines a number with a unit — file sizes, bitrates, framerates, scale factors. The parser always produces {value, unit} and the command builder formats it per tool.
dimensions is kept distinct from quantity because width × height is a two-number compound with its own parsing rules and shorthand (1080p → 1920x1080). Tools also consume it differently — ffmpeg wants -vf scale=1920:1080, not a single token.
enum replaces any slot that selects from a predefined set of values — codecs, interpolation filters, output modes, and so on. The valid options are declared on the slot itself via the values field. Multi-select is just enum + cardinality = "many".
codec = { category = "ARGUMENT", type = "enum", values = ["h264", "h265", "vp9", "av1"], required = false, render = "flag", flag = "-c:v", desc = "Video codec to encode with" }string is an explicit last resort for genuinely free-text inputs. If you find yourself reaching for it frequently it is a signal that a more specific type is missing.
When a slot accepts multiple values (cardinality = "many"), expansion defines how those values are passed to the tool at command construction time:
| Expansion | Behaviour |
|---|---|
inline |
All values in one invocation: mv a.txt b.txt dest/
|
loop |
One invocation per value: ffmpeg -i a.mp4 ..., then ffmpeg -i b.mp4 ...
|
Note that execution = "loop" on the command level and expansion = "loop" on a slot are distinct: command-level loop iterates the entire invocation per target, slot-level expansion only affects how that slot's values are passed within a single invocation.
When a query is processed, the pipeline selects the best capability using three mechanisms in order:
1. Intent triggers — narrows candidates to capabilities whose triggers list matches the classified intent.
2. Adapter forcing — if the user specifies "using <alias>", the candidate set is filtered to that adapter only before scoring. The forcing phrase is stripped from the query before slot extraction.
3. Slot fitting — each remaining candidate is scored by how well the extracted spans fill its slots. The candidate with the highest confidence wins. When a required slot cannot be filled and no default is specified, that candidate is disqualified.
Capabilities with destructive = true require a dry-run confirmation step before execution. This applies to any operation that cannot be trivially undone — moves, deletes, truncations.
The dry-run prints the resolved command and affected files and waits for explicit confirmation before proceeding.
- Create a
.tomlfile underadapters/ - Define the
[adapter]block with a name and aliases - Declare one or more
[[domains]]blocks - Declare one or more
[[capabilities]]blocks, each referencing a domain by name - Validate against the schema — see
schemas/adapter.schema.json
If you are using VSCode with the Even Better TOML extension, schema validation and autocomplete are enabled automatically for files under adapters/. See .vscode/settings.json.