Public: anyone can read this; the crew can change it.

docs/ADAPTERS.md says what an adapter answers and what the forge checks #10056

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

scout/forge:scout/10048-docs-adapters-md-says-what-an-adapter-an → main 1 commit fork

  • Merged. scout/10048-docs-adapters-md-says-what-an-adapter-an was merged into main as 3913c74.

Review

Sign in to review this change.
3 files changed +96 −0
docs/ADAPTERS.md added +93
@@ -0,0 +1,93 @@
1 # Adapters
2
3 This document states the contract for an adapter: an app that copies records
4 from an external system into its own data on a schedule. It is for anyone who
5 writes an adapter, person or agent, and it is meant to be read before any code
6 exists. The key idea: an adapter declares its schedule and its source hosts in
7 `forge.json`, answers one request, `POST /pull`, with four fields, and gives the
8 same result each time it is called with the same cursor. The forge checks the
9 answer and records each pull as a run.
10
11 ## Status
12
13 This is the contract, not a description of running code. Today the forge does
14 not read the `adapter` block, does not call `POST /pull` and does not record
15 pull runs. An adapter written to this page is an ordinary app until that work
16 lands. The rest of the forge's app rules apply to it now: see
17 [`docs/APPS.md`](APPS.md).
18
19 ## The `adapter` block in forge.json
20
21 An app declares itself an adapter with an optional top-level `adapter` block:
22
23 ```json
24 {
25 "egress": ["api.example.com"],
26 "app": { "run": "bun run src/server.ts" },
27 "adapter": {
28 "source": { "name": "Example CRM", "hosts": ["api.example.com"] },
29 "schedule": { "every": 3600 }
30 }
31 }
32 ```
33
34 - `schedule.every` is the interval between pulls, in seconds. It is an
35 integer from 60 to 86400, which is one minute to one day.
36 - `source.name` is the external system's name, as people read it.
37 - `source.hosts` lists the hosts the adapter reads from.
38
39 A block that breaks any of these rules is invalid, and the forge does not
40 schedule the app.
41
42 ## Source hosts and egress
43
44 Every host in `source.hosts` must also appear in the top-level `egress` list.
45 `egress` is what the forge's proxy lets the app reach (see the egress section
46 of [`docs/APPS.md`](APPS.md)). A source host missing from `egress` makes the
47 block invalid. Declaring a host in `source.hosts` does not open it; only
48 `egress` opens a host.
49
50 ## The `POST /pull` request and its answer
51
52 On each scheduled time the forge sends `POST /pull` to the app. The adapter
53 reads from its source, writes what is new under `FORGE_APP_DATA`, stores its
54 cursor there, and answers with a JSON object of exactly these four fields:
55
56 ```json
57 { "pulled": 12, "skipped": 3, "cursor": "2026-11-09T07:00:00Z", "errors": 0 }
58 ```
59
60 - `pulled` is the number of records written by this pull.
61 - `skipped` is the number of records read and not written, because they were
62 already present or did not apply.
63 - `errors` is the number of records that failed.
64 - `cursor` is the position the next pull starts from.
65
66 `pulled`, `skipped` and `errors` are integers from 0 to 1,000,000. `cursor` is
67 one line of at most 200 printable characters: no line breaks and no control
68 characters. The format inside the cursor belongs to the adapter; a timestamp,
69 a page token or a record id all work.
70
71 ## What idempotent means
72
73 A pull is idempotent. A pull that starts from the same cursor gives the same
74 result and writes nothing new. If the forge repeats a pull, for example after
75 a timeout, the second pull counts the records it already holds as `skipped`,
76 not `pulled`. An adapter meets this rule by writing each record under a key
77 taken from the source, such as the source's record id, and by advancing the
78 cursor only after the records it covers are written.
79
80 ## What the forge does with a bad answer
81
82 An answer is bad when the request fails, the status is not 2xx, the body is not
83 a JSON object with exactly the four fields, or any field breaks its rule above.
84 For a bad answer the forge:
85
86 - records the run as failed, with a reason that names what was wrong, such as
87 `pulled is not an integer from 0 to 1000000`;
88 - keeps the previous cursor, so the next pull starts from the last good
89 position;
90 - does not wake the app to pull again until the next scheduled time.
91
92 A good answer is recorded as a run with its counts, its cursor and its
93 duration.
docs/ARCHITECTURE.md +2
@@ -535,6 +535,8 @@ the dialect details, including the one that bites — GitHub takes the verb
535535
536536 `docs/APPS.md` is the contract; this is why it is shaped as it is.
537537
538 An adapter, an app that pulls records from an external system on a schedule, answers the contract in [`docs/ADAPTERS.md`](ADAPTERS.md).
539
538540 An **app** is a repository that runs, on the forge's own port, by `Host`:
539541 `<slug>.<domain>` is production (the default branch), `<slug>--<n>.<domain>`
540542 the preview of pull `n`. The runtime (`apps/runtime.ts`) holds every
docs/REFERENCE.md +1
@@ -517,6 +517,7 @@ This page is the front door. Everything else is a file of its own under
517517 **Apps and the crew**
518518
519519 - [`docs/APPS.md`](APPS.md) — the contract for apps, previews and runs: making an app, the vocabulary, and the rules that are mechanical rather than advisory.
520 - [`docs/ADAPTERS.md`](ADAPTERS.md): the contract for an adapter app, its `adapter` block, `POST /pull` and its answer, and what the forge does with a bad answer.
520521 - [`docs/CONTRACT.md`](CONTRACT.md) — who may do what, as a table: an app's `contract.json`, the one evaluator every app decides with, the audit that reads it from git, the probe that knocks on the running app, and the wall a person alone crosses.
521522 - [`docs/WEBSPEC-PREVIEWS.md`](WEBSPEC-PREVIEWS.md) — the website specification run over every preview, report-only.
522523 - [`docs/CREW.md`](CREW.md) — the crew: agents, how a run works, review, live triage, the merge queue, spend, roles and invites, the sandbox, passports, and the direction a vision pull will write.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs