| D-M2-37-1 |
2026-10-25 |
Page, language and nav: the guide is docs/guides/connect-agent.md, in English like the site, in a new nav section Guides after API; it addresses a developer who connects an own adapter. Sections in this order: Placeholders, Prerequisites, the ten steps with the bold titles of chapter 11 section 13 (without the final period), "How the repository checks this guide", Next. |
One place for the guides of the site; the step titles bind the page to chapter 11. |
| D-M2-37-2 |
2026-10-25 |
Python is the only executable path: the guide's code is the example adapter in sdk/python/examples/guide/; TypeScript gets one sentence in step 4 with links to sdk/typescript/README.md and its reference. No Mermaid block; the page links the chapters instead. |
One executable path keeps the check exact; every fence must be executable or compared. |
| D-M2-37-3 |
2026-10-25 |
Owner steps through the API: every owner step shows the operation; one sentence says that the admin screens of app and console call the same operations. |
The API is the path a developer can follow today and the one every screen uses. |
| D-M2-37-4 |
2026-10-25 |
Block grammar: every fence starts at column 0 with a language and title="..."; kinds request (http, <Owner\|Alex\|Sam>: <operationId>), response (json, Response <status>), shell (bash, Shell: <text>), adapter start and stop (bash), output (text), file (python, the repository path) and pin (toml, pyproject.toml (excerpt)); fences only in Prerequisites and the steps; there is no actor Agent. The grammar is described in services/server/test/guide/README.md. |
The contract between the text and the test; an untitled or unknown block cannot slip through unchecked. |
| D-M2-37-5 |
2026-10-25 |
Placeholders and secrets: the runner binds {server_url}, {adapter_dir}, {alex_id}, {sam_id}, {new_uuid} and {expires_at} (start time plus half of auth.api_token_max_ttl), listed exactly in the table of section Placeholders; every other name is captured once by a response or output block before its first use and compared afterwards; "{name}" keeps its JSON type; bash values are single-quoted and must have the form of a shell value. {secret:<file>} stands only in a response block: written to the adapter folder with mode 0600, never compared, never logged, redacted from every error. |
The page reads like a guide with placeholders while every value is still checked; a token never appears in the text or the logs (T-TK-02, SEC-LOG-2). |
| D-M2-37-6 |
2026-10-25 |
Matching: JSON bodies are expected subsets (objects by member, arrays element by element in any order with exactly one distinct match each, an element matching several is ambiguous); output lines are compared in order, an unbound placeholder captures \S+; a process that ends early or leaves unexpected lines fails. Every failure names the step, the block title and line and, for JSON, the JSON Pointer; nothing after it runs. |
Answers carry many members the guide need not show; the order of array elements is not part of the contract. |
| D-M2-37-7 |
2026-10-25 |
Actors: Owner is the first owner of the harness server and does a fresh step-up before each request, as the admin screens do (08 6.2); Alex (trigger with approve_actions on Support) and Sam (write) are created by the harness; the harness client adds the session token, DPoP and Idempotency-Key. |
No owner block can fail with step_up_required; the people of the white-label examples. |
| D-M2-37-8 |
2026-10-25 |
Example layout and output: one program in small step modules (keygen, agent, connect, heartbeat, commands, invocation, runtime, run, interactive, tests/test_agent.py), each shown whole in its step; one stdout line per confirmed action, Step <n>: <text>, with run, thread and component IDs but never content, prompt, token, key or a person's name; SDK logs to stderr at WARNING. The start lines come once per process in the order 4, 5, 6: on_ready prints the connection (getMe) and sends one sendHeartbeat (the gateway heartbeat is acknowledged before its agent part is applied, M2-20), the commands line follows the reconciliation, and prepare waits for all three. A run the inbox held before the start prints "resuming ... from the inbox" instead of the acceptance line; --stop-after accept stops the adapter from prepare after the acceptance line, so the entry stays accepted. Commands are prefixed with PYTHONPATH=sdk/python/src, because the SDK project is not installable before its release chain (M2-23/1). |
The guide shows the whole program; the output is deterministic and safe to print. |
| D-M2-37-9 |
2026-10-25 |
Rule-based runtime: fixed rules on the trigger text replace a model (case-insensitive, first match wins: follow-up, question, approval, long; the model command summarize; anything else a fixed answer); usage is fixed token counts without amount_micros. |
The guide costs nothing and its output is the same on every run (PLAN 3 point 5). |
| D-M2-37-10 |
2026-10-25 |
Runner in Go (services/server/test/guide, component testsupport) on the shared harness of M1-71 (harness.Start, NewUser, StepUp, AdminDB); ServerConfig raises ratelimit.*, points the scan at the scripted clamd (D-M2-37-11) and sets command.autocomplete_timeout to testkit.Patience plus a minute (http.request_timeout above it), as the autocomplete tests of M2-17 do, because the default would bound step 8 by the speed of the machine (red team finding, class fix with TestServerConfig_NoStepIsBoundByTheServersSpeed); commands run in the repository root with the test's environment minus every BOTHY_ variable and without bytecode files, each in its own process group; every wait ends at the test's deadline. The pin is read with a TOML subset parser (tables, strings, arrays of strings), so no new Go module is added. |
The owner and the people need the device-key and DPoP clients of the Go test support; no second harness. |
| D-M2-37-11 |
2026-10-25 |
Files: MessageCreate.attachment_file_ids is enabled on the base branch (M3-07 merged), so step 9 has the fifth part: Alex asks for a file, the example uploads a small text file through createUpload, uploadPart and completeUpload of the generated core (the hand layer has no file module), sends it with attachment_file_ids (the parameter of Outbox.send_message that M3-12 added in parallel), the heartbeat reports files_out, and getMessage shows the attachment's original_name. The test server scans with the scripted clamd of the M3-07 tests (clamdtest.Fake, file.scan.clamd_address). Alex's grant carries upload besides approve_actions, because a run acts with the rights of its trigger (SR9): without it createUpload answers 403 forbidden with reason acting_for_insufficient. |
The text states only what the server does; the file setup is the one the merged attachment tests use. |
| D-M2-37-12 |
2026-10-25 |
Examples follow every rule of hand-written code of sdk-python: SPDX header, ruff with D (tests excluded) and the formatter, strict mypy (files covers examples/guide), bandit (the root make lint excludes */examples/*/tests/* like tests/), pytest collects examples/guide/tests with examples/guide on the path, and coverage measures examples/guide next to bothy_sdk. |
The example is the code developers copy. |
| D-M2-37-13 |
2026-10-25 |
CI path selection: docs/guides/connect-agent.md and docs/spec/11-extensions.md select server and sdk-python; sdk/python/examples/ selects server through the existing prefix sdk/python/. |
The check run is a server test that reads these files. |
| D-M2-37-14 |
2026-10-25 |
Suggestions are shown in step 8, in Sam's post, right before the model command: the server answers an autocomplete request in a forum channel without thread_id with an empty list, because a forum has no main thread to bind the token to (R-AZ-26). Step 6 registers and lists the commands and says so. |
The brief placed the autocomplete in step 6, before any thread exists; the server follows the specification. |
| D-M2-37-15 |
2026-10-25 |
SDK additions (smallest public names, each with a test): AgentHandler.on_commands_reconciled (the commands line after the reconciliation), AgentHandler.on_autocomplete_sent (the suggestion line after the send), RuntimeStatus.reported_billing_mode and RuntimeStatus.billing_evidence (step 5 reports mode and evidence with every heartbeat, not only once). SDK fix: expectation_for compares the command_id of a command-triggered agent.invocation (M2-16 added it; the SDK still refused every model command). |
The example uses only public SDK names and writes no protocol code; the check run found the refusal. |
| D-M2-37-16 |
2026-10-25 |
A run from a model command loads no context: the example takes the rule from command_name and the options and prints that the run comes from the command. getRunContext answers internal_error for a run whose trigger_kind is not message until commands and interactions define their context (09 4.6, M2-13). |
The server follows its specification; the gap is reported for the ticket that defines the context of commands. |