mistri 0.5.0 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +480 -4
- data/CONTRIBUTING.md +52 -0
- data/README.md +289 -385
- data/SECURITY.md +40 -0
- data/UPGRADING.md +640 -0
- data/assets/logo-animated.svg +30 -0
- data/assets/logo-dark.svg +14 -0
- data/assets/logo-light.svg +14 -0
- data/assets/logo.svg +14 -0
- data/assets/social-preview.png +0 -0
- data/docs/README.md +87 -0
- data/docs/context-and-workspaces.md +378 -0
- data/docs/mcp.md +366 -0
- data/docs/reliability.md +450 -0
- data/docs/sessions.md +295 -0
- data/docs/sub-agents.md +401 -0
- data/docs/tool-contracts.md +324 -0
- data/examples/approval.rb +36 -0
- data/examples/browser.rb +27 -0
- data/examples/page_editor.rb +31 -0
- data/examples/quickstart.rb +21 -0
- data/lib/generators/mistri/install/install_generator.rb +7 -3
- data/lib/generators/mistri/install/templates/migration.rb.tt +2 -2
- data/lib/generators/mistri/mcp/templates/migration.rb.tt +1 -1
- data/lib/generators/mistri/mcp/templates/model.rb.tt +15 -8
- data/lib/mistri/agent.rb +575 -55
- data/lib/mistri/budget.rb +26 -1
- data/lib/mistri/child.rb +72 -16
- data/lib/mistri/compaction.rb +26 -10
- data/lib/mistri/compactor.rb +34 -11
- data/lib/mistri/console.rb +28 -7
- data/lib/mistri/content.rb +9 -3
- data/lib/mistri/dispatchers.rb +14 -12
- data/lib/mistri/errors.rb +83 -4
- data/lib/mistri/event.rb +24 -8
- data/lib/mistri/event_delivery.rb +60 -0
- data/lib/mistri/locks.rb +3 -3
- data/lib/mistri/mcp/client.rb +74 -19
- data/lib/mistri/mcp/egress.rb +216 -0
- data/lib/mistri/mcp/oauth.rb +476 -127
- data/lib/mistri/mcp/wires.rb +115 -23
- data/lib/mistri/mcp.rb +42 -8
- data/lib/mistri/message.rb +21 -11
- data/lib/mistri/models.rb +160 -22
- data/lib/mistri/providers/anthropic/assembler.rb +282 -44
- data/lib/mistri/providers/anthropic/serializer.rb +14 -9
- data/lib/mistri/providers/anthropic.rb +29 -6
- data/lib/mistri/providers/fake.rb +26 -10
- data/lib/mistri/providers/gemini/assembler.rb +148 -21
- data/lib/mistri/providers/gemini/serializer.rb +78 -9
- data/lib/mistri/providers/gemini.rb +31 -5
- data/lib/mistri/providers/openai/assembler.rb +337 -60
- data/lib/mistri/providers/openai/serializer.rb +13 -12
- data/lib/mistri/providers/openai.rb +29 -5
- data/lib/mistri/providers/schema_capabilities.rb +214 -0
- data/lib/mistri/result.rb +1 -1
- data/lib/mistri/schema.rb +893 -75
- data/lib/mistri/session.rb +560 -48
- data/lib/mistri/sinks/coalesced.rb +17 -10
- data/lib/mistri/spawner.rb +111 -61
- data/lib/mistri/sse.rb +57 -14
- data/lib/mistri/stores/active_record.rb +11 -4
- data/lib/mistri/stores/memory.rb +21 -2
- data/lib/mistri/sub_agent/execution.rb +81 -0
- data/lib/mistri/sub_agent/runtime.rb +297 -0
- data/lib/mistri/sub_agent.rb +124 -87
- data/lib/mistri/task_output.rb +24 -6
- data/lib/mistri/tool.rb +93 -13
- data/lib/mistri/tool_arguments.rb +377 -0
- data/lib/mistri/tool_call.rb +43 -9
- data/lib/mistri/tool_context.rb +4 -2
- data/lib/mistri/tool_executor.rb +117 -26
- data/lib/mistri/tool_result.rb +15 -10
- data/lib/mistri/tools/edit_file.rb +62 -8
- data/lib/mistri/tools.rb +41 -4
- data/lib/mistri/transport.rb +149 -44
- data/lib/mistri/usage.rb +65 -13
- data/lib/mistri/version.rb +1 -1
- data/lib/mistri/workspace/active_record.rb +190 -5
- data/lib/mistri/workspace/directory.rb +28 -8
- data/lib/mistri/workspace/memory.rb +34 -9
- data/lib/mistri/workspace/single.rb +62 -5
- data/lib/mistri/workspace.rb +39 -0
- data/lib/mistri.rb +6 -1
- data/mistri.gemspec +34 -0
- metadata +31 -3
data/docs/mcp.md
ADDED
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
# Model Context Protocol
|
|
2
|
+
|
|
3
|
+
Mistri can turn tools from a Model Context Protocol server into ordinary local
|
|
4
|
+
tools. The Agent's validation, approval, event, session, and result contracts
|
|
5
|
+
then apply to the remote call.
|
|
6
|
+
|
|
7
|
+
The built-in client supports Streamable HTTP and stdio with no additional gem
|
|
8
|
+
dependency. `Mistri::MCP.tools` is duck-typed, so a different client can bridge
|
|
9
|
+
when it responds to `tools` and `call_tool(name, arguments)` with the documented
|
|
10
|
+
hash shapes.
|
|
11
|
+
|
|
12
|
+
## Transports
|
|
13
|
+
|
|
14
|
+
### Streamable HTTP
|
|
15
|
+
|
|
16
|
+
```ruby
|
|
17
|
+
client = Mistri::MCP::Client.new(
|
|
18
|
+
url: "https://mcp.linear.app/mcp",
|
|
19
|
+
token: -> { connection.bearer_token },
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
tools = Mistri::MCP.tools(
|
|
23
|
+
client,
|
|
24
|
+
prefix: "linear",
|
|
25
|
+
allow: %w[search_issues get_issue create_issue],
|
|
26
|
+
gates: { "create_issue" => true },
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
agent = Mistri.agent("claude-opus-4-8", tools: tools)
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
`token:` accepts a String or callable. The callable resolves for every request.
|
|
33
|
+
After a 401, Mistri resolves it once more and retries once, so the host can put
|
|
34
|
+
refresh behavior behind the callable.
|
|
35
|
+
|
|
36
|
+
Use `headers:` for fixed application headers. Mistri copies them at client
|
|
37
|
+
construction and rejects framing plus MCP session and protocol headers. A fixed
|
|
38
|
+
Authorization header is permitted; when `token:` is supplied, Mistri owns
|
|
39
|
+
Bearer authorization and overwrites it.
|
|
40
|
+
|
|
41
|
+
One Client serializes its requests. Parallel Agent tool calls against the same
|
|
42
|
+
client queue rather than interleave. Use distinct clients only when the server
|
|
43
|
+
and host policy allow true parallel connections.
|
|
44
|
+
|
|
45
|
+
### Stdio
|
|
46
|
+
|
|
47
|
+
```ruby
|
|
48
|
+
browser = Mistri::MCP::Client.new(
|
|
49
|
+
command: [
|
|
50
|
+
"npx", "-y", "@playwright/mcp@latest",
|
|
51
|
+
"--browser", "chrome", "--headless",
|
|
52
|
+
],
|
|
53
|
+
read_timeout: 180,
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
tools = Mistri::MCP.tools(
|
|
57
|
+
browser,
|
|
58
|
+
prefix: "web",
|
|
59
|
+
allow: %w[browser_navigate browser_snapshot],
|
|
60
|
+
)
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
`command:` is an argument array, not a shell string. Pass credentials through
|
|
64
|
+
`env:` when the child process needs them. Mistri writes one JSON-RPC object per
|
|
65
|
+
line and bounds the complete record read, including the time after its first
|
|
66
|
+
byte.
|
|
67
|
+
|
|
68
|
+
`@latest` is convenient for local exploration. Pin a reviewed package version
|
|
69
|
+
in an application or deployment so the executable does not change without a
|
|
70
|
+
code review.
|
|
71
|
+
|
|
72
|
+
Call `client.close` when a long-lived host is finished. For stdio, it terminates
|
|
73
|
+
the child. For HTTP, it closes the local connection and forgets the negotiated
|
|
74
|
+
session, but does not send the optional MCP DELETE termination request; remote
|
|
75
|
+
resources expire by server policy. The discovered tool cache remains available.
|
|
76
|
+
`call_tool` or `tools(refresh: true)` performs a fresh handshake when needed.
|
|
77
|
+
|
|
78
|
+
## Tool discovery
|
|
79
|
+
|
|
80
|
+
`client.tools` performs the handshake and follows `tools/list` pagination once,
|
|
81
|
+
then caches the result. Use `client.tools(refresh: true)` when the host decides
|
|
82
|
+
to accept a changed toolset.
|
|
83
|
+
|
|
84
|
+
Discovery defaults to at most 100 pages and 10,000 tools:
|
|
85
|
+
|
|
86
|
+
```ruby
|
|
87
|
+
client = Mistri::MCP::Client.new(
|
|
88
|
+
url: server_url,
|
|
89
|
+
max_tool_pages: 20,
|
|
90
|
+
max_tools: 2_000,
|
|
91
|
+
)
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Repeated cursors, malformed collections, and exceeded limits raise instead of
|
|
95
|
+
looping or allocating without bound.
|
|
96
|
+
|
|
97
|
+
`Mistri::MCP.tools` filters remote names with `allow:` and `deny:`. `prefix:`
|
|
98
|
+
creates local names such as `linear__create_issue` while the remote call still
|
|
99
|
+
uses `create_issue`. Duplicate local tool names make Agent construction fail;
|
|
100
|
+
one server can never silently shadow another.
|
|
101
|
+
|
|
102
|
+
`needs_approval:` gates every bridged tool. `gates:` sets policy by remote name
|
|
103
|
+
and overrides the shared default.
|
|
104
|
+
|
|
105
|
+
## Schema authority
|
|
106
|
+
|
|
107
|
+
Mistri validates the directly reachable portable subset described in
|
|
108
|
+
[Tool contracts](tool-contracts.md). The MCP server remains responsible for its
|
|
109
|
+
full advertised input schema, as required by the
|
|
110
|
+
[MCP tools contract](https://modelcontextprotocol.io/specification/2025-11-25/server/tools).
|
|
111
|
+
|
|
112
|
+
Standard assertions outside Mistri's subset, such as `minimum`, `pattern`,
|
|
113
|
+
`format`, and `anyOf`, bridge as server-facing guidance. An unsupported
|
|
114
|
+
applicator subtree remains one guidance unit; Mistri does not partially enforce
|
|
115
|
+
inside it and imply a stronger guarantee.
|
|
116
|
+
|
|
117
|
+
When local policy depends on the full schema, supply a complete validator:
|
|
118
|
+
|
|
119
|
+
```ruby
|
|
120
|
+
tools = Mistri::MCP.tools(
|
|
121
|
+
client,
|
|
122
|
+
complete_argument_validator: lambda { |args, schema|
|
|
123
|
+
host_json_schema_validator.errors(args, schema)
|
|
124
|
+
},
|
|
125
|
+
)
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
The callback must implement the whole contract and return an Array of Strings.
|
|
129
|
+
Core ownership and its portable checks still run first.
|
|
130
|
+
|
|
131
|
+
An argument-applicable, non-empty `patternProperties` requires this explicit
|
|
132
|
+
complete authority. Same-document references can remain server guidance;
|
|
133
|
+
external references are rejected even with a complete validator so validation
|
|
134
|
+
never performs hidden file or network resolution.
|
|
135
|
+
|
|
136
|
+
An omitted `inputSchema` means a closed no-argument object. Explicit null,
|
|
137
|
+
non-object roots, malformed keyword shapes, and an explicit older dialect are
|
|
138
|
+
configuration errors. Mistri compiles MCP input as JSON Schema 2020-12.
|
|
139
|
+
|
|
140
|
+
## Results and large data
|
|
141
|
+
|
|
142
|
+
Mistri maps MCP text, images, embedded resources, and `resource_link` into its
|
|
143
|
+
content model. `structuredContent` is used when no ordinary content is present
|
|
144
|
+
and is also retained in an error explanation. On a successful result containing
|
|
145
|
+
both fields, ordinary content currently wins and `structuredContent` is not
|
|
146
|
+
preserved.
|
|
147
|
+
|
|
148
|
+
`isError` becomes `ToolResult#error?` and remains structured in events and the
|
|
149
|
+
session. Anthropic receives native `is_error`; Gemini receives its error result
|
|
150
|
+
key. OpenAI's function-result shape has no error member, so it receives the same
|
|
151
|
+
explanatory content without a separate flag.
|
|
152
|
+
|
|
153
|
+
Large results should live behind a server or host resource. Return an MCP
|
|
154
|
+
`resource_link` and a concise description instead of embedding an unbounded
|
|
155
|
+
payload. Mistri renders the URI for the model and never fetches it automatically;
|
|
156
|
+
fetching credentials, retention, and access policy belong to the host or server.
|
|
157
|
+
|
|
158
|
+
## Response boundaries
|
|
159
|
+
|
|
160
|
+
Provider and MCP JSON bodies, individual SSE lines, and stdio records default
|
|
161
|
+
to an 8 MiB `max_record_bytes:` limit:
|
|
162
|
+
|
|
163
|
+
```ruby
|
|
164
|
+
client = Mistri::MCP::Client.new(
|
|
165
|
+
url: trusted_server_url,
|
|
166
|
+
max_record_bytes: 16 * 1024 * 1024,
|
|
167
|
+
)
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
The limit applies to one atomic record, not the lifetime of an SSE stream.
|
|
171
|
+
Successful HTTP bodies are counted incrementally, and a declared oversized body
|
|
172
|
+
fails before it is read. Requests use identity encoding so compressed expansion
|
|
173
|
+
cannot bypass the boundary.
|
|
174
|
+
|
|
175
|
+
Each completed SSE JSON record also receives fixed structural ceilings before
|
|
176
|
+
JSON parsing: 20,001 lexical tokens, 64 levels, and 64 KiB per numeric token.
|
|
177
|
+
These protect allocation even when a record stays under its byte limit.
|
|
178
|
+
|
|
179
|
+
Byte overflow raises `Mistri::ResponseTooLargeError`. Structural overflow raises
|
|
180
|
+
`Mistri::ResponseTooComplexError`. Both expose `kind` and `limit`.
|
|
181
|
+
|
|
182
|
+
Either failure resets the wire and negotiated session. The next operation must
|
|
183
|
+
handshake again.
|
|
184
|
+
|
|
185
|
+
### Ambiguous tool delivery
|
|
186
|
+
|
|
187
|
+
A response boundary or wire failure after `tools/call` is different from an
|
|
188
|
+
ordinary read failure: the server may have received and committed the operation
|
|
189
|
+
before the response disappeared.
|
|
190
|
+
|
|
191
|
+
When no matching result was confirmed, Mistri raises
|
|
192
|
+
`Mistri::AmbiguousDeliveryError` and never replays the call automatically. The
|
|
193
|
+
message tells the caller to verify external state before any retry.
|
|
194
|
+
|
|
195
|
+
That is the direct Client contract. A tool bridged through `Mistri::MCP.tools`
|
|
196
|
+
runs inside the normal tool executor, which converts the raised exception into
|
|
197
|
+
an error-marked tool result for the model and host. The transport still does not
|
|
198
|
+
replay the call. A model may choose to issue a new call after reading the error,
|
|
199
|
+
so approval, idempotency, and reconciliation remain host policy.
|
|
200
|
+
|
|
201
|
+
If a matching result arrived before an invalid trailing SSE record, that result
|
|
202
|
+
remains authoritative. The wire is still reset for the next operation.
|
|
203
|
+
|
|
204
|
+
Session-expired and authentication retries occur only through explicit server
|
|
205
|
+
responses that establish the request was not accepted under the old session or
|
|
206
|
+
credential. An ambiguous outcome never takes those replay paths.
|
|
207
|
+
|
|
208
|
+
## Remote network policy
|
|
209
|
+
|
|
210
|
+
Remote MCP and server-side OAuth URLs are untrusted input. By default Mistri:
|
|
211
|
+
|
|
212
|
+
- requires public HTTPS;
|
|
213
|
+
- resolves and validates every DNS answer against special-purpose address
|
|
214
|
+
ranges, including embedded NAT64 destinations;
|
|
215
|
+
- rejects the whole result if any answer is unsafe;
|
|
216
|
+
- pins an approved address while preserving the hostname for Host, SNI, and
|
|
217
|
+
certificate verification;
|
|
218
|
+
- resolves and validates again for each new connection cycle, including an
|
|
219
|
+
internal keep-alive reconnect;
|
|
220
|
+
- tries alternate approved addresses only before request bytes are sent;
|
|
221
|
+
- follows no redirects;
|
|
222
|
+
- ignores ambient HTTP proxy settings.
|
|
223
|
+
|
|
224
|
+
Private HTTPS servers require one narrow host callback:
|
|
225
|
+
|
|
226
|
+
```ruby
|
|
227
|
+
require "ipaddr"
|
|
228
|
+
|
|
229
|
+
private_range = IPAddr.new("10.20.0.0/16")
|
|
230
|
+
allow_internal = lambda do |uri, address|
|
|
231
|
+
%w[mcp.internal.example auth.internal.example].include?(uri.hostname) &&
|
|
232
|
+
private_range.include?(address)
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
client = Mistri::MCP::Client.new(
|
|
236
|
+
url: "https://mcp.internal.example/mcp",
|
|
237
|
+
allow_non_public: allow_internal,
|
|
238
|
+
)
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
The callback is consulted only for otherwise blocked addresses. Match both the
|
|
242
|
+
expected hostname and an explicit IP range. It cannot permit an invalid URL.
|
|
243
|
+
Plain HTTP remains limited to an explicitly approved address that actually
|
|
244
|
+
resolves to loopback, which is suitable only for local development:
|
|
245
|
+
|
|
246
|
+
```ruby
|
|
247
|
+
allow_loopback = ->(_uri, address) { address.loopback? }
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
Pass the same policy to the Client and every OAuth operation for an internal
|
|
251
|
+
deployment.
|
|
252
|
+
|
|
253
|
+
## OAuth
|
|
254
|
+
|
|
255
|
+
`Mistri::MCP::OAuth` implements the storage-agnostic OAuth 2.1 flow required by
|
|
256
|
+
MCP. The host owns the connection record, callback route, token encryption, and
|
|
257
|
+
authorization-server selection.
|
|
258
|
+
|
|
259
|
+
Start discovery and registration:
|
|
260
|
+
|
|
261
|
+
```ruby
|
|
262
|
+
flow = Mistri::MCP::OAuth.start(
|
|
263
|
+
url: server_url,
|
|
264
|
+
client_name: "YourApp",
|
|
265
|
+
redirect_uri: callback_url,
|
|
266
|
+
scope: requested_scope,
|
|
267
|
+
allow_non_public: allow_internal,
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
# Persist the returned flow, then send the user's browser to this URL.
|
|
271
|
+
authorize_url = flow.fetch("authorize_url")
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
Persist at least the state, code verifier, client ID and secret, token auth
|
|
275
|
+
method, issuer, resource, and redirect URI. `token_endpoint` is returned for
|
|
276
|
+
compatibility and display only; later operations rediscover it from the exact
|
|
277
|
+
issuer.
|
|
278
|
+
|
|
279
|
+
The host must compare callback state with the persisted value before exchange:
|
|
280
|
+
|
|
281
|
+
```ruby
|
|
282
|
+
unless host_secure_compare(callback_state, flow.fetch("state"))
|
|
283
|
+
raise "invalid OAuth state"
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
tokens = Mistri::MCP::OAuth.complete(
|
|
287
|
+
code: params.fetch(:code),
|
|
288
|
+
code_verifier: flow.fetch("code_verifier"),
|
|
289
|
+
client_id: flow.fetch("client_id"),
|
|
290
|
+
client_secret: flow["client_secret"],
|
|
291
|
+
token_auth_method: flow.fetch("token_auth_method"),
|
|
292
|
+
issuer: flow.fetch("issuer"),
|
|
293
|
+
resource: flow.fetch("resource"),
|
|
294
|
+
redirect_uri: flow.fetch("redirect_uri"),
|
|
295
|
+
allow_non_public: allow_internal,
|
|
296
|
+
)
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
Refresh tokens can rotate, so persist a returned replacement:
|
|
300
|
+
|
|
301
|
+
```ruby
|
|
302
|
+
tokens = Mistri::MCP::OAuth.refresh(
|
|
303
|
+
refresh_token: connection.refresh_token,
|
|
304
|
+
client_id: connection.client_id,
|
|
305
|
+
client_secret: connection.client_secret,
|
|
306
|
+
token_auth_method: connection.token_auth_method,
|
|
307
|
+
issuer: connection.issuer,
|
|
308
|
+
resource: connection.url,
|
|
309
|
+
allow_non_public: allow_internal,
|
|
310
|
+
)
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
An authorization server may omit `refresh_token` or `scope` from a successful
|
|
314
|
+
refresh. Low-level callers must retain the current stored value unless the
|
|
315
|
+
response supplies a replacement. The generated Active Record adapter already
|
|
316
|
+
does this.
|
|
317
|
+
|
|
318
|
+
Mistri requires S256 PKCE. It follows protected-resource and authorization-server
|
|
319
|
+
discovery, binds metadata to exact resource and issuer identifiers, and honors
|
|
320
|
+
challenge scope. It does not add `offline_access` as gem policy.
|
|
321
|
+
|
|
322
|
+
When discovery advertises multiple authorization servers, the host must pass
|
|
323
|
+
`issuer:` explicitly. A pre-registered `client_id` always requires the exact
|
|
324
|
+
issuer trusted when it was provisioned. Servers without dynamic registration
|
|
325
|
+
also accept `client_secret:` and an explicit supported `token_auth_method:`.
|
|
326
|
+
|
|
327
|
+
OAuth response bodies are uncompressed and bounded to 256 KiB. Provider error
|
|
328
|
+
descriptions are not copied into credential-bearing exceptions.
|
|
329
|
+
|
|
330
|
+
## Optional Rails connection generator
|
|
331
|
+
|
|
332
|
+
Rails applications can generate an encrypted connection model and migration:
|
|
333
|
+
|
|
334
|
+
```console
|
|
335
|
+
$ bin/rails generate mistri:mcp McpConnection
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
Each row stores one server, OAuth flow state, issuer, encrypted tokens, expiry,
|
|
339
|
+
and scope. The generated model exposes `connect`, `complete`, token refresh, a
|
|
340
|
+
Client, and bridged tools. Set up Active Record encryption before storing
|
|
341
|
+
credentials.
|
|
342
|
+
|
|
343
|
+
Override the generated class policy when the server is internal:
|
|
344
|
+
|
|
345
|
+
```ruby
|
|
346
|
+
class McpConnection < ApplicationRecord
|
|
347
|
+
def self.mcp_allow_non_public
|
|
348
|
+
ALLOW_INTERNAL_MCP
|
|
349
|
+
end
|
|
350
|
+
end
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
The generator is an adapter, not a requirement. The same OAuth services work in
|
|
354
|
+
Sinatra, Hanami, GraphQL, jobs, or any host with a durable record and callback
|
|
355
|
+
route.
|
|
356
|
+
|
|
357
|
+
Generated application files are copies and do not change when the gem updates.
|
|
358
|
+
Read the [upgrade guide](../UPGRADING.md) before upgrading an existing generated
|
|
359
|
+
connection model.
|
|
360
|
+
|
|
361
|
+
## Related guides
|
|
362
|
+
|
|
363
|
+
- [Tool contracts](tool-contracts.md)
|
|
364
|
+
- [Sessions and control](sessions.md)
|
|
365
|
+
- [Reliability](reliability.md)
|
|
366
|
+
- [Upgrading](../UPGRADING.md)
|