skill ↔ backend — two orthogonal axes
In Metnos «skill» and «backend» answer two different
questions. The backend says HOW a
verb_object runs against a service. The skill says
WHETHER and WHICH capabilities are unlocked, trusted and
shipped. Conflating them leads to splitting executors apart; keeping them
separate is what lets you add a provider without touching the planner.
| Backend / provider | Skill | |
|---|---|---|
| Answers | HOW I run verb_object against a service | WHETHER / WHICH capabilities are unlocked, trusted, shipped |
| Axis | object × provider | external dependency (credential / backend / hardware) |
| Visibility | invisible to the LLM, resolved from configuration by the backend resolver | user-facing (enable / disable, dormancy, sandbox, packaging) |
The two axes touch only when a skill has a single provider: in
that case github, geo, mail are in
practice the user-facing name of that provider's backend. They
diverge as soon as a skill has several backends (a calendar is
served over local ICS, Google or CalDAV) or has no
external provider at all (photos, the core). The map is
many-to-many: one provider (Google Workspace) serves several
skills — mail, calendar, drive, sheets, docs.
find_issues.
«Skill» ended up meaning three different things: a classification of
executors already in the repository, real packages in the repository, and
imported third-party code. The tier field unifies them under a
single model. It lives in skills_catalog.py (function
skill_tier()) and in the SKILL.md front matter.
| Tier | What it is | Trust | Where it lives | Activation |
|---|---|---|---|---|
| 1 — core | planner and engine, local files, dirs, processes, time, local scheduler, persons, credentials, signatures, proposals, the scratchpad helpers (filter / sort / group / compute / describe / extract) | maximum | repository, signed, multilingual | always on |
| 2 — first_party | capabilities shipped with Metnos at the same standard as the core, but dormant until the dependency is present: github, photos, mail, web, geo, calendar, generic data store (sqldatabase), frontier, Google Workspace | maximum | repository (versioned bundle + signed executors) | automatic, dormant if not configured |
| 3 — imported | third-party code, not audited nor vendored (from agentskills.io and the like) | low | user data, with tracked provenance | opt-in, sandbox + 7-layer net |
Tomorrow GitLab might arrive alongside GitHub. The question is: how do you add a second provider for the same issues? Two paths.
Generate find_issues_github and
find_issues_gitlab and let the planner choose. This works where a
frontier model sits at the center and disambiguates by reading prose. In Metnos
the planner is a local model, which faced with two nearly
identical names develops a provider bias:
it always picks the same one, or gets it wrong. Splitting
breaks routing.
find_issues, read_issues, find_pulls.
The object (issues, pulls) is the stable
abstraction.runtime/backends/{issues,pulls}/{github,gitlab}.py.github skill (PAT)
and a gitlab skill (token), with independent dormancy —
because the credential is the boundary where granularity is needed
(«enable gitlab» without touching github)._github / _gitlab
stays for the user's explicit pin («open the issue
on gitlab»); the default is agnostic.+1 backend file,
+1 skill (if it introduces a new credential),
0 new executors. It is the path calendar and files have already
taken.
provider:access capability declares the skill binding and may be narrowed
with when to the final client selector value. Only the effective
binding governs network access, credentials, and the server pin together; neither the
executor name nor the argument value is a source of permission.
sqldatabase skill.
The abstract shape is not a promise for the future: the sqldatabase
skill already embodies it. It offers three canonical executors —
find_entries, write_entries,
delete_entries — that read, write and delete records in named
stores (find_entries(store="expenses", …)), while the query
never knows where the data actually lives. Underneath sit two interchangeable
backends built on a shared base (SqlDatabaseBackend): one persistent
on SQLite, one ephemeral in memory; tomorrow PostgreSQL joins as a sibling
without touching the executors. Which backend serves a given store is decided
in a registry, when the store is declared — never from the
user's request. The skill stays dormant until a store is
registered.
find_issues_github + find_issues_gitlab confuse the local planner). On the right the abstracted state: a single find_issues, the resolver, two backends. The arrow is promotion: refactoring with a one-off frontier, then human review. GitHub is currently in the «baked-in» state.*_github executors (issues, PRs, tasks, comments and — since
25/6 — repo files: find/read/list) carry the provider in the name. It
works while the provider stays unique; the moment a second one arrives it must
be abstracted. General rule: a provider that plausibly will have siblings
(github→gitlab/gitea, IMAP mail, calendar) should be abstracted from the
start; one that will stay unique may remain baked-in.
In the multi-provider model the skill does not turn the executor on and off: it turns the backend on and off. This changes who sees what.
find_issues is available if
at least one of its backends is enabled and configured; it goes
dormant only when all of them are off (an AND across all
backends).find_issues — it only changes routing. Disable one of the
two: the other keeps serving. Disable both: the executor goes dormant.
Today gating is by pattern on the executor name
(skills_catalog): it works only while the provider is unique. The
new piece to build is a skill→backend map plus the rule
«executor dormant = AND over all its backends being off». It is the
first brick: needed as soon as the first multi-backend executor exists.
Merging a baked-in, mono-provider skill (*_github) into
find_issues + backends is an extraordinary
operation. A one-off frontier LLM is allowed ONLY for the
refactoring; detection and apply stay deterministic
and human-gated — auto-signing frontier-generated code would break the
trust model.
| Phase | Who | What |
|---|---|---|
| WHEN (trigger) | deterministic | on the 2nd provider's arrival — typically the import of a skill that maps onto an object already covered only by provider-suffixed executors (*_github, recognized by the naming authority). Object overlap + provider mismatch → «promotion candidate». No LLM needed to spot it. |
| WHERE (seam) | reused infrastructure | detection in the importer admission layer; refactoring via an escalation to a frontier model in agentic mode; landing in the admin changes page as a change of kind promote_provider, no auto-apply; apply writes the files, re-signs (§7.10), registers skill↔backend and per-backend gating (§4). |
| HOW (pipeline) | frontier only in the middle | detect (det.) → the frontier generates {canonical executor + backends/<obj>/{p1,p2}.py + skill→backend map} → the change is proposed → human review → apply + sign + verify with the 7-layer admission + smoke. |
MCP and the executor contract solve different problems. MCP lets a client discover and invoke tools exposed by a server while negotiating protocol versions and capabilities. Metnos must instead decide what a capability means to the planner, what authority it has, and how its outcome is verified.
The contract referenced here is not an implicit convention: it is the versioned Metnos Executor Standard. An MCP backend must satisfy it just like a local or remote backend.
| Concern | MCP provides | Metnos must preserve |
|---|---|---|
| identity | a tool name within one server | a provider-independent canonical verb_object name |
| contract | a JSON argument schema; an optional result schema | vector shape, error semantics, and an observable postcondition |
| authority | connection and authorization to the server | capabilities, sandbox, credential mandate, and consent for the action |
| lifecycle | negotiation, calls, cancellation, and shutdown | limits, audit, idempotency, reverse operation, and recovery |
| catalog | notifications when tools are added or changed | explicit admission and suspension when a schema changes |
mcp_executor(server, tool, args) would introduce a second
command language and bypass per-action controls, so it does not belong in the
public contract.
An external tool may become the backend of an existing executor, a narrow proxy executor, or remain quarantined. Turning it into a native executor would be a verified, reviewed promotion rather than an automatic rewrite of remote code.
| To understand… | Read |
|---|---|
| how a third-party skill is imported and turned into signed executors | skill importer |
| the layered sandbox imported executors run in | sandbox |
| the checks before every risky action | vaglio |
| what an executor is and how it is built | executor |
| the other axis of swappability: how the model (LLM, embedding, VLM) behind the capabilities is chosen and changed, by editing a TOML instead of the code | model virtualization |
Metnos — skill ↔ backend, two orthogonal axes