Two adapter tiers

An adapter is what a step's use line names, either a YAML binding the runner interprets or a program speaking NDJSON, with one manifest surface

What you'll learn

You need a vendor gtme doesn't ship, or you're reading someone else's adapter and want to know what it can do before you install it.

  • what a binding can and can't express, and when an integration becomes a process adapter
  • how the registry installs a binding pinned and verified
  • why plan treats both tiers the same, and what simulate does with each
  • what the built-in floor adapters are for

Here's a vendor the gtme binary doesn't ship. Search the registry, a public index of bindings on GitHub. This spends nothing and sends nothing:

gtme adapters search hubspot

The output is the following:

ID                      ROLE    TIER      INSTALL                                   DESCRIPTION
hubspot/contact-search  source  verified  gtme adapters add hubspot/contact-search  Source HubSpot contacts via the CRM v3 Search API, filtered…

Install it with the line in the INSTALL column:

gtme adapters add hubspot/contact-search

The output is similar to the following:

fetched github.com/gtme-run/gtme-bindings/hubspot-contact-search@bcf671b9e1df177ed24e98b58e0c132176261c78 at bcf671b9e1df
hubspot/contact-search v1 — source (person)
  calls:       api.hubapi.com
  demands:     HUBSPOT_ACCESS_TOKEN
  needs:       none
  provides:    company_name, email, first_name, hubspot.contact_id, hubspot.lifecycle_stage, last_name, title
  fixtures:    ok — 1 response(s) on file, 2 record(s) extracted
...

add verified the adapter before installing it. calls: is every host it contacts, demands: is its credential, and needs: and provides: are the fields it reads and writes. fixtures: says its recorded vendor response ran offline and produced records. In the search output, TIER is the registry's trust level: verified means the registry's own tests run the fixtures. The adapter's tier is KIND, in the next listing.

What you just saw

An adapter is whatever a step's use: line names, and it comes in two tiers. Here's what's installed after also copying in the gtme repository's example process adapter, adapters/mock-enrich-py:

gtme adapters

The output is the following:

ID                      VERSION  ROLE    KIND     SOURCE
hubspot/contact-search  1        source  binding  github.com/gtme-run/gtme-bindings/hubspot-contact-search@bcf671b9e1df177ed24e98b58e0c132176261c78 (bcf671b9e1df)
mock-enrich-py          1        enrich  process  installed by hand

SOURCE shows the commit the registry pinned for the ID. Both live in ~/.gtme/adapters/.

A binding is a YAML file the runner (gtme itself) interprets. Here's the head of yours, trimmed:

id: hubspot/contact-search
version: 1
role: source
entity_type: person

credentials: [HUBSPOT_ACCESS_TOKEN]
...
request:
  method: POST
  url: "{{config.base_url}}/crm/v3/objects/contacts/search"
  body:
    filterGroups:
      - filters:
          - propertyName: "{{config.known_property}}"
            operator: HAS_PROPERTY
...
extract:
  records: results
  fields:
    email:                   properties.email
    first_name:              properties.firstname
    last_name:               properties.lastname
    title:                   properties.jobtitle
    company_name:            properties.company
    hubspot.contact_id:      id
    hubspot.lifecycle_stage: properties.lifecyclestage
...

request: is a template filled from the step's with: values. extract.fields maps each response path to a canonical field or a hubspot. name. Auth, pagination, errors, retries, and cost are settings in the same file, and none of them can hold a formula or an if.

A process adapter is a program, here a folder holding manifest.json and run. Here's the manifest, trimmed:

{
  "id": "mock-enrich-py",
  "version": 1,
  "role": "enrich",
  "entity_type": "person",
  "needs": {
...
  "provides": {
...
      "mock.score": {"type": "integer", "minimum": 0, "maximum": 100},
      "mock.note": {"type": "string"}
...

run is an executable in any language that reads and writes records as NDJSON, newline-delimited JSON with one object per line.

Here's the test for your vendor. A documented HTTP API you call with a key is a binding. Anything needing a conditional, several calls per record, OAuth or request signing (login schemes a fixed template can't perform), or its own computation is a process adapter.

Both tiers present the same manifest surface: id, version, role, entity type, needs, provides, the with: keys a step may set, and credentials. gtme plan reads only that surface. Neither tier touches the ledger; the runner writes what they return.

So what?

One pipeline can use both. This file sources from the binding and scores with the process adapter:

name: tiers
version: 1

source:
  use: hubspot/contact-search
  with:
    known_property: email
    limit: 2

steps:
  - id: score
    use: mock-enrich-py

gtme plan tiers.yaml stops here, because plan checks declared credentials (setting one):

gtme: step "source": missing credential HUBSPOT_ACCESS_TOKEN (set it in the environment or run `gtme secret set HUBSPOT_ACCESS_TOKEN`)

Simulate needs no key. It serves a binding from its fixture, and runs an uncredentialed process adapter like this one for real:

gtme run tiers.yaml --simulate

The receipt at the end is similar to the following:

run 01M3FBF44H1KBCN64FH9TMV50G — done (SIMULATED — recorded responses only; nothing sent, nothing persisted)
step    adapter                 in  out  empty  cached  filtered  failed  cost  avoided
source  hubspot/contact-search  0   2    -      0       -         -       $0    -
score   mock-enrich-py          2   2    -      0       -         -       $0    -
total: $0 (estimated) spent

The source row is HubSpot's recorded response, and the score row is the Python program scoring it.

Hand your agent the search and add lines, because add installs nothing that fails verification. If your vendor isn't listed, hand it this, which prints the binding schema and a reference binding as JSON:

gtme help --bindings

That's it. That's how adapters work, and every vendor you add is one of these two.

Why it's this way

Most vendor APIs are plain HTTP, so most adapters are data. The decision record, ADR-022, runs every binding through one HTTP engine in the runner. A binding can't run code, so a stranger's binding is safe to read and add can list its hosts. SPEC calls the binding-or-program test the graduation rule (SPEC §10a).

Vendors live in a registry outside the binary. gtme adapters update moves a pin only when you ask, and every registry entry must ship fixtures (ADR-042).

The binary ships a floor: the csv/*, http/*, sql/*, ai/*, and group/* families (the last for record groups). It carries no vendor: every vendor adapter is a registry entry you install with gtme adapters add (ADR-059). Most are bindings; instantly/add-to-campaign is a Go process adapter the registry serves prebuilt and pinned by checksum, because its preflight checks are logic a binding may not hold (ADR-063). The floor is the crudest version of any integration on purpose, so anything is wireable today (ADR-023). Inline http/* config that repeats across pipelines should become a binding. We think that's the right trade: a pipeline runs on day one and improves when a binding replaces its floor step.

The binary also ships built-ins outside the floor: the participant adapters human/* and agent/*, demo/enrich, and text/compose.

What it gave up. A process adapter is code you install by hand, so read it first, and it can't travel inside a bundle.

Where it shows up

Three guides and a start page write adapters, and four reference pages list them.