Skip to content

Commit de65e76

Browse files
author
tadasant
committed
docs: demote Goose from framing device to illustration
Each client mechanism now leads with the general shape any client can implement; Goose specifics follow in parentheses as one concrete instance. Drops the verbatim goose:// deep-link template.
1 parent d57f37a commit de65e76

1 file changed

Lines changed: 40 additions & 31 deletions

File tree

docs/best-practices.md

Lines changed: 40 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,11 @@ The probe itself is always the same and always cheap: one asynchronous
9797
asked for. The design decision is not _how_ to probe — it is **which moments** in a session
9898
should trigger one.
9999

100+
Where a concrete example helps, this section points at [Goose](https://goose-docs.ai/), Block's
101+
open-source MCP agent, simply because it is open source and its hooks are easy to read. Nothing
102+
here is Goose-specific — every mechanism below has an equivalent in any client that runs tools,
103+
reads project files, or mediates network access.
104+
100105
#### Start here: probe the domains a user hands you
101106

102107
**At minimum, we recommend a default-on experience that probes any domain a user enters as a
@@ -121,10 +126,10 @@ flowchart TD
121126
```
122127

123128
A closely related and equally bounded set: the domains a **project** already points at — links
124-
in a `.goosehints` file (Goose injects these into the system prompt and supports literal
125-
`https://` URLs), an `AGENTS.md`, or a recipe's configuration. A `SessionStart` or
126-
`UserPromptSubmit` hook can probe that set once per session. These are the domains the project
127-
is built around, the user put them there deliberately, and there are only ever a handful.
129+
in an `AGENTS.md`, a project config, or whatever hints file your client injects into the system
130+
prompt (in Goose, `.goosehints`, which supports literal `https://` URLs). Probe that set once at
131+
session start rather than on every turn. These are domains the project is built around, the user
132+
put them there deliberately, and there are only ever a handful.
128133

129134
#### Expand carefully: broader triggers, off by default for now
130135

@@ -136,29 +141,30 @@ interacted with them.**
136141
```mermaid
137142
flowchart LR
138143
A["User-entered URLs<br/>(recommended, default-on)"] --> P[Shared probe + cache]
139-
B["Project files<br/>(.goosehints, AGENTS.md, recipes)"] --> P
144+
B["Project files<br/>(AGENTS.md, hints files, recipes)"] --> P
140145
C["Tool-call results<br/>(web fetch, scrape)"] -.opt-in.-> P
141146
D["Network egress boundary<br/>(every domain reached)"] -.opt-in.-> P
142147
P --> E[domain → catalog map, misses cached]
143148
E --> F[Install offer]
144149
```
145150

146151
- **Tool-call results.** The user or the agent chose to retrieve a page, which is close to
147-
direct intent. Goose exposes this through its
148-
[lifecycle hooks](https://goose-docs.ai/docs/guides/context-engineering/hooks) — a
149-
`hooks.json` maps events such as `PreToolUse` / `PostToolUse` to scripts, with a `matcher`
150-
regular expression selecting which tool the rule runs for (the docs match tool names like
151-
`developer__shell|developer__text_editor`). Match a `PreToolUse` rule to a web-fetch tool —
152-
Goose's Computer Controller extension, for instance, exposes a web-scrape tool — and the hook receives the tool input as JSON, including the target URL,
153-
so it can fire the probe without modifying the tool itself.
154-
- **The network egress boundary.** The broadest option: if your client already mediates
155-
network access, that chokepoint sees every domain the agent actually reaches. Goose's
156-
[macOS sandbox](https://goose-docs.ai/docs/guides/sandbox/) is built this way — the seatbelt
157-
sandbox denies direct network access and forces outbound traffic through a local proxy that
158-
evaluates each destination against a `blocked.txt` list. Discovery can ride the same seam as
159-
that filtering. It composes with an allow/deny boundary you may already run, but it is also
160-
where the noise is worst: most domains publish no catalog, so the caching in
152+
direct intent. If your client can intercept tool calls, select the URL-bearing ones — a web
153+
fetch or scrape — and read the target host out of the tool's input before or alongside the
154+
call. This needs no change to the tool itself, and you stay in control of _which_ tools
155+
trigger a probe. (Goose implements this shape with
156+
[lifecycle hooks](https://goose-docs.ai/docs/guides/context-engineering/hooks): a `hooks.json`
157+
maps `PreToolUse` / `PostToolUse` to scripts, with a `matcher` regex selecting the tool and
158+
the tool input handed to the hook as JSON.)
159+
- **The network egress boundary.** The broadest option: if your client already mediates network
160+
access, that chokepoint sees every domain the agent actually reaches, not just the ones a tool
161+
or file surfaced — so discovery can ride the same seam as the filtering you already do there.
162+
It composes with an allow/deny boundary you may already run, but it is also where the noise is
163+
worst: most domains publish no catalog, so the caching in
161164
[Keep probing cheap](#keep-probing-cheap-and-let-enterprises-scope-it) matters most here.
165+
(Goose's [macOS sandbox](https://goose-docs.ai/docs/guides/sandbox/) is built this way — a
166+
seatbelt sandbox denies direct network access and forces outbound traffic through a local
167+
proxy that checks each destination against a list.)
162168

163169
**We do not recommend turning these on by default at this time.** Ship them opt-in, behind a
164170
setting, while the ecosystem and the interaction pattern are still young — the default-on
@@ -168,18 +174,21 @@ guidance may change.
168174

169175
### Turn a hit into a one-click install
170176

171-
A Goose extension _is_ an MCP server, so an MCP Server Card maps straight onto Goose's
172-
existing [install path](https://goose-docs.ai/docs/getting-started/using-extensions) — no new
173-
machinery. When a probe finds an entry, surface it to the user (interrupting the turn or
174-
presenting it passively is your call) and offer a `goose://extension?...` deep link, the same
175-
format Goose's extensions directory generates:
176-
177-
```
178-
goose://extension?url=<streamable-http-url>&type=streamable_http&id=<id>&name=<name>&description=<description>
179-
```
180-
181-
All parameters are URL-encoded; alternatively, write the equivalent block into Goose's
182-
`config.yaml`. Either way the server is added and connected **mid-turn**.
177+
You almost certainly do not need new machinery for this. Your client already has a way to add
178+
an MCP server — a config file, an install command, a deep link from an extensions directory —
179+
and a Server Card carries exactly what that path needs: the endpoint, the transport, and the
180+
identity to display. Discovery just supplies those values from a catalog instead of from a user
181+
who typed them.
182+
183+
So when a probe finds an entry, surface it (interrupting the turn or presenting it passively is
184+
your call) and route the accept into the install path you already have. The one thing worth
185+
insisting on is that the server is added and connected **mid-turn** — the value here is the
186+
user not having to leave what they were doing.
187+
188+
For a concrete instance: in Goose an extension _is_ an MCP server, so a card maps onto its
189+
existing [install path](https://goose-docs.ai/docs/getting-started/using-extensions) directly —
190+
either the `goose://extension?...` deep link its extensions directory generates, or the
191+
equivalent block written into `config.yaml`.
183192

184193
### Security and trust considerations
185194

0 commit comments

Comments
 (0)