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
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1414bffbf49921cf9e575ea1a8998cfc435b77325d80d55ed76e6564d63311c5
|
|
4
|
+
data.tar.gz: b707010a75d56d65a8c42b50a874807e0f529da6b5878246aa9b34239493c808
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b7781114db5365b8ce899af9ab50b8eaf2279979d8110a282d54a4fb3187a2576fc94821c3ddb550e9af3da8052ad64b14310c5136d3a064044aee57418c209b
|
|
7
|
+
data.tar.gz: d87fb8db281d3c6cedaea995054723f4f15d62e5268384393e16d655dfd45ba714e4b276c73c378307e8b3314ecec1108b3d320578edd659d77e6d07a8e34e2f
|
data/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,473 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
5
5
|
|
|
6
|
+
## [Unreleased]
|
|
7
|
+
|
|
8
|
+
## [0.6.1] - 2026-07-21
|
|
9
|
+
|
|
10
|
+
- The ActiveRecord store and workspace read past the host's query cache.
|
|
11
|
+
Rails keeps the cache on for a job's whole span and serves repeated
|
|
12
|
+
identical queries from it, so a parent polling for a child's report inside
|
|
13
|
+
a job never saw the report land and every wait ran to its timeout, and a
|
|
14
|
+
workspace read could serve a stale document. Reads bypass only Rails'
|
|
15
|
+
cache: an open REPEATABLE READ transaction still pins its snapshot, so do
|
|
16
|
+
not poll from inside one.
|
|
17
|
+
|
|
18
|
+
## [0.6.0] - 2026-07-12
|
|
19
|
+
|
|
20
|
+
- Synchronous event subscribers now propagate the exact exception object even
|
|
21
|
+
when its class also names an internal failure: provider `Mistri::Error`
|
|
22
|
+
values, transport I/O and timeout errors, SSE `JSON::ParserError`, hook
|
|
23
|
+
failures, and automatic-compaction `CompactionError` can no longer be
|
|
24
|
+
swallowed, retried, folded into an errored assistant turn, or persisted as a
|
|
25
|
+
policy or tool failure. An owner-scoped private marker preserves provenance
|
|
26
|
+
through nested inline agents and unwraps only at the boundary that first
|
|
27
|
+
observed the subscriber. A mid-stream subscriber failure closes the
|
|
28
|
+
interrupted provider connection before propagating. SSE parse tolerance now
|
|
29
|
+
covers `JSON.parse` only, never its callback. Genuine provider, hook, tool,
|
|
30
|
+
and compaction failures keep their existing contracts; configured tool
|
|
31
|
+
timeouts still include progress delivery. Asynchronous background delivery
|
|
32
|
+
remains unable to raise into a caller that has already returned. A callback
|
|
33
|
+
that aborts child execution records the original failure class in its failed
|
|
34
|
+
terminal and dispatcher diagnostic; a post-terminal report callback failure
|
|
35
|
+
leaves the already-durable terminal and parent inbox intact and appears in
|
|
36
|
+
the diagnostic. Active subscribers add one short-lived boundary per provider
|
|
37
|
+
turn, tool batch, hook phase, or automatic compaction and one method/rescue
|
|
38
|
+
frame per delivered event, with no parsing, buffering, I/O, or string work on
|
|
39
|
+
the successful path.
|
|
40
|
+
- Approval decisions now behave as a write-once register over the Session
|
|
41
|
+
store's durable order. The first valid decision is authoritative; repeating
|
|
42
|
+
that value is idempotent, while a conflicting caller receives
|
|
43
|
+
`Mistri::ConfigurationError`. Concurrent stale writers may both reach append,
|
|
44
|
+
but every later well-formed decision for that approval request is an inert
|
|
45
|
+
losing occurrence, including one that lands after the approved handler has
|
|
46
|
+
already returned. It cannot revoke the winner, alter settlement, or
|
|
47
|
+
permanently poison `open_approvals`, `resume`, replay, or compaction. The first
|
|
48
|
+
note remains authoritative.
|
|
49
|
+
Malformed decisions, decisions without a matching request, duplicate
|
|
50
|
+
requests, and ambiguous legacy reused-call approvals still fail closed or
|
|
51
|
+
interrupt conservatively. A newly appended decision adds one linear
|
|
52
|
+
control-state re-read to tell a racing loser that it lost; already-visible
|
|
53
|
+
idempotent retries do not append. No provider streaming path changes.
|
|
54
|
+
- Anchored `edit_file` changes now compose safely with concurrent writers when
|
|
55
|
+
their Workspace advertises atomic conditional writes. The new optional
|
|
56
|
+
contract is `atomic_writes?`, `snapshot(path)`, and
|
|
57
|
+
`compare_and_write(path, content, expected_revision:)`, with immutable
|
|
58
|
+
`Mistri::Workspace::Snapshot` values and a public
|
|
59
|
+
`Mistri::WorkspaceConflictError`. Memory implements the contract under its
|
|
60
|
+
mutex; the optional Active Record adapter advertises it only for PostgreSQL
|
|
61
|
+
or an InnoDB table on MySQL2 or Trilogy, using exact-byte SHA-256 revisions,
|
|
62
|
+
unique-index create-only CAS, and short transactions without requiring
|
|
63
|
+
`lock_version`. It rejects atomic operations inside a host transaction rather
|
|
64
|
+
than joining a stale or overlong boundary, validates model identity against
|
|
65
|
+
the database catalog, restricts atomic scope to owned scalar equality values,
|
|
66
|
+
and reads the committed row before returning its revision. Single opts in
|
|
67
|
+
through a host `synchronize:` callback that must enforce the same complete
|
|
68
|
+
boundary and outer-transaction rule. Directory, MyISAM, and existing custom
|
|
69
|
+
four-method workspaces retain their documented legacy behavior. `edit_file`
|
|
70
|
+
applies its existing exact/fuzzy anchor outside any lock and retries only a
|
|
71
|
+
lost conditional write, up to three total attempts. An unrelated concurrent
|
|
72
|
+
change is preserved, while a target that no longer matches remains an
|
|
73
|
+
actionable typed edit failure. Callback, validation, I/O, deadlock, and other
|
|
74
|
+
host errors are never retried. Tool names and schemas are unchanged. A
|
|
75
|
+
committed storage transformation produces an explicit
|
|
76
|
+
read-before-continuing failure instead of a false exact-replacement claim.
|
|
77
|
+
Atomic editing adds owned snapshot copies, three exact-byte linear hashes,
|
|
78
|
+
one exact committed-byte comparison, and one conditional commit to the
|
|
79
|
+
ordinary edit path; Memory also returns an owned read copy. Conflicts add at
|
|
80
|
+
most two complete reapplications. Active Record capability adds cached schema
|
|
81
|
+
inspection on the first capability check, outside the edit path. No
|
|
82
|
+
provider streaming path changes. Dedicated MySQL 8.4/InnoDB and PostgreSQL CI
|
|
83
|
+
jobs prove real locking, readback, and unique-create behavior while the gem
|
|
84
|
+
retains zero runtime dependencies.
|
|
85
|
+
- Built-in document reads, searches, and edits now return an explicitly failed
|
|
86
|
+
`ToolResult` when the document is missing or a requested replacement cannot
|
|
87
|
+
be applied. The same actionable text still reaches the model, but lifecycle
|
|
88
|
+
events, persisted tool messages, and provider-supported error signaling no
|
|
89
|
+
longer misclassify an expected document-tool failure as success. Tool names,
|
|
90
|
+
schemas, and successful results are unchanged.
|
|
91
|
+
- Background dispatch now crosses a versioned, deeply owned JSON capability
|
|
92
|
+
boundary instead of closing over the spawn-time provider, Tool objects, Agent
|
|
93
|
+
options, and workspace state. Every `dispatcher:` requires a host
|
|
94
|
+
`runtime_factory:` that constructs a `SubAgent::Runtime` inside the worker;
|
|
95
|
+
the runtime provider model and unique Tool names must match the durable grant
|
|
96
|
+
exactly before any provider call or tool side effect. Missing, additional,
|
|
97
|
+
renamed, duplicated, non-Tool, reconstructed statically approval-gated, and
|
|
98
|
+
synthetic Skill capabilities fail closed before execution. New immutable
|
|
99
|
+
specs carry `spec_version`, require a model identity, reject unknown fields,
|
|
100
|
+
and omit the model-controlled `workspace` field. The identical canonical
|
|
101
|
+
spec is stored in the child Session before dispatch; `run_dispatched` compares
|
|
102
|
+
the complete queue copy and uses the stored grant for execution and report
|
|
103
|
+
routing, so a changed capability, task, model, name, or parent ID leaves the
|
|
104
|
+
child untouched. Legacy unversioned specs containing `workspace` remain
|
|
105
|
+
executable. Finished redeliveries are rejected before factory construction;
|
|
106
|
+
a configured child lease also rejects concurrent live redeliveries. The
|
|
107
|
+
compatible direct form receives the same grant checks.
|
|
108
|
+
Runtime factory exceptions remain retryable for queues by default; built-in
|
|
109
|
+
in-process runners and an explicit `retry_factory_errors: false` persist a
|
|
110
|
+
terminal failure before re-raising. An optional Runtime `cleanup:` hook runs
|
|
111
|
+
exactly once without masking a primary error. Generic child Agent options
|
|
112
|
+
are allowlisted and travel as one isolated Hash, so they cannot replace the
|
|
113
|
+
durable Session, task, signal, event sink, or other lifecycle state; Runtime
|
|
114
|
+
cannot add Skill capabilities. The dispatched
|
|
115
|
+
lease now spans runtime construction, execution, cleanup, terminal
|
|
116
|
+
persistence, and parent report delivery; it suppresses ordinary stop and
|
|
117
|
+
redelivery races while the tokenless lease remains live. Queued and
|
|
118
|
+
interrupted cancellations now deliver their durable stopped report while holding that
|
|
119
|
+
lease even when the queue never starts or retries the job; repeating stop
|
|
120
|
+
reconciles a transient parent-report failure. A queue copy that does not
|
|
121
|
+
match the persisted grant raises the public, non-retryable
|
|
122
|
+
`Mistri::DispatchGrantError`, which is reserved for grant binding.
|
|
123
|
+
Model selection records only its identity at spawn time; its provider is
|
|
124
|
+
constructed in the worker. `tools: []` now grants no tools, while omission
|
|
125
|
+
keeps the general pool or typed defaults, so an empty typed Definition can no
|
|
126
|
+
longer inherit the entire pool. Duplicate pool, model, or model-selected tool
|
|
127
|
+
names fail before a child starts, and Spawner owns copies of its policy
|
|
128
|
+
arrays. Dispatch adds one bounded spec ownership/comparison and one linear
|
|
129
|
+
capability-name check per child, outside all provider streaming paths.
|
|
130
|
+
- Model-originated tool calls now cross a local execution boundary rather than
|
|
131
|
+
relying on provider guidance alone. Completed calls deeply own immutable JSON,
|
|
132
|
+
canonicalize symbol keys without coercing values, retain malformed encoded
|
|
133
|
+
input as a durable `arguments_error`, and stop at 8 MiB, 64 levels, 10,000
|
|
134
|
+
nodes, or a 64 KiB numeric token. The same numeric bound applies to encoded
|
|
135
|
+
JSON and programmatic custom-provider arguments. The Agent requires an object
|
|
136
|
+
argument envelope before `before_tool`, approval predicates, `ends_turn`,
|
|
137
|
+
handlers, or remote MCP calls. Invalid calls receive a bounded typed result in
|
|
138
|
+
band, emit no execution-start event, expose no received field values in core
|
|
139
|
+
errors, and do not prevent valid siblings from running. Results settled in
|
|
140
|
+
the same phase return in model-call order.
|
|
141
|
+
Completed tool-call IDs, names, signatures, and provider correlation IDs must
|
|
142
|
+
be non-empty UTF-8 strings. Live provider turns require IDs unique for the
|
|
143
|
+
session, and provider correlation IDs are unique within one assistant turn.
|
|
144
|
+
Persisted histories recognize only the `call_N` reuse without provider
|
|
145
|
+
correlation IDs that earlier releases synthesized for Gemini and the Fake
|
|
146
|
+
provider, after the prior occurrence was answered or crash-interrupted.
|
|
147
|
+
Replay, provider metadata, and compaction track each occurrence independently
|
|
148
|
+
while live IDs remain reserved for the full session. An unsettled reused-ID
|
|
149
|
+
approval that cannot distinguish its generation becomes an interrupted result
|
|
150
|
+
rather than executable stale authorization. Every other duplicate ID,
|
|
151
|
+
malformed call envelope, or non-assistant provider result rejects the whole
|
|
152
|
+
provider attempt before persistence, policy, or execution. The normal
|
|
153
|
+
provider retry contract retries unchanged history; exhaustion persists a
|
|
154
|
+
pairable error with no tool calls. An abort during normalization, validation,
|
|
155
|
+
policy, or approval evaluation interrupts every uncommitted call and parks no
|
|
156
|
+
approval. Custom
|
|
157
|
+
providers that emitted missing, blank, non-string, non-UTF-8, or duplicate
|
|
158
|
+
identifiers must correct their completed call contract. Each run or resume
|
|
159
|
+
validates every persisted message and audits call identity in one control-state
|
|
160
|
+
preflight before doing work, so malformed legacy history fails closed. The
|
|
161
|
+
preceding assistant entry remains the durable provider source; a normalized
|
|
162
|
+
approval stores the reviewed prepared call plus an explicit provenance
|
|
163
|
+
marker. Resume verifies pairing metadata and revalidates the exact approved
|
|
164
|
+
arguments without rerunning the normalizer. Requests must follow the exact
|
|
165
|
+
assistant call, and decisions must follow one request and carry an exact
|
|
166
|
+
boolean. Duplicate requests and malformed or mismatched controls fail closed.
|
|
167
|
+
The first valid durable decision wins; later well-formed decisions for that
|
|
168
|
+
approval request are inert. Legacy requests remain valid only when they
|
|
169
|
+
exactly mirror the source call.
|
|
170
|
+
Persisted tool results require one prior call with the exact name, settle in
|
|
171
|
+
assistant-call order within each direct or approval phase, and cannot cross
|
|
172
|
+
an unresolved approval; compaction may neither hide an open approval nor
|
|
173
|
+
split a completed call from its result.
|
|
174
|
+
`ToolCall#arguments_error?` is stable; its non-nil string remains an opaque
|
|
175
|
+
diagnostic rather than a public enum. Fake-provider default call IDs now use
|
|
176
|
+
a per-provider UUID namespace plus a monotonic sequence; explicitly scripted
|
|
177
|
+
`id:` values remain deterministic. New approval entries no longer duplicate
|
|
178
|
+
the source argument payload, and generated MySQL-family session tables use
|
|
179
|
+
LONGTEXT so a legal parallel tool turn is not constrained by MEDIUMTEXT's
|
|
180
|
+
16 MiB ceiling. Existing MySQL-family stores must migrate `payload` to
|
|
181
|
+
`size: :long`; generators cannot alter an installed table. `Stores::Memory`
|
|
182
|
+
owns durable appends while returning fresh mutable snapshots, matching stores
|
|
183
|
+
that decode each read.
|
|
184
|
+
- Tool schemas compile once into the same deeply frozen canonical UTF-8 JSON
|
|
185
|
+
value used by provider serializers, so mutation and Ruby encoding cannot make
|
|
186
|
+
local validation disagree with the wire. Definition checks cover the complete
|
|
187
|
+
schema document. The zero-dependency runtime subset enforces JSON types and
|
|
188
|
+
unions, `enum`, `required`, `properties`, `prefixItems`, one-schema `items`,
|
|
189
|
+
boolean schemas, and boolean or schema-valued `additionalProperties`.
|
|
190
|
+
`argument_validator:` adds domain rules without weakening core;
|
|
191
|
+
`complete_argument_validator:` is separate explicit authority for every
|
|
192
|
+
argument-applicable non-empty `patternProperties` contract. Tool inputs use
|
|
193
|
+
JSON Schema 2020-12 and require an object root; legacy array-form `items` is
|
|
194
|
+
rejected in favor of `prefixItems`. A schema-less Tool is now a genuinely
|
|
195
|
+
closed no-argument contract; hosts that accepted model fields without
|
|
196
|
+
declaring them must add an explicit schema. The schema-less wire shape
|
|
197
|
+
changed with it, so provider prompt caches re-warm once per affected tool
|
|
198
|
+
set after upgrading. Supplied object schemas preserve
|
|
199
|
+
JSON Schema's default-open semantics unless a raw schema explicitly sets
|
|
200
|
+
`additionalProperties: false`; handlers should extract named fields instead
|
|
201
|
+
of mass-assigning model input. MCP bridging enforces directly reachable
|
|
202
|
+
portable constraints locally and keeps unsupported applicator subtrees and
|
|
203
|
+
other unimplemented standard assertions as guidance for the server, which the
|
|
204
|
+
MCP spec obligates to validate its own tool inputs. A complete validator takes
|
|
205
|
+
explicit whole-contract authority when local policy depends on those
|
|
206
|
+
assertions. External references and
|
|
207
|
+
explicit `inputSchema: null` are rejected in every mode, an omitted schema
|
|
208
|
+
still defaults to a no-argument object, and argument-applicable non-empty
|
|
209
|
+
`patternProperties` still requires complete authority. Common map schemas
|
|
210
|
+
work in core. Task mode rejects assertions local validation cannot guarantee
|
|
211
|
+
before making a provider request, then shares one deeply frozen strict schema
|
|
212
|
+
between its prompt and compiled local validator.
|
|
213
|
+
Providers derive a native constraint only for schema shapes their documented
|
|
214
|
+
and live-verified subsets accept; incompatible shapes fall back to the prompt
|
|
215
|
+
and local correction loop instead of failing the request. Native constraints
|
|
216
|
+
are limited to catalogued matching-provider models and documented provider
|
|
217
|
+
complexity ceilings; unknown models keep the local path. Raw task output is
|
|
218
|
+
byte-bounded, then lexically
|
|
219
|
+
width/depth-bounded before JSON parsing so wide hostile JSON is rejected
|
|
220
|
+
before parser allocation. A Tool-owned `argument_normalizer:` remains the
|
|
221
|
+
explicit compatibility seam and runs once before validation; approved calls
|
|
222
|
+
persist that canonical value and revalidate against the current schema on
|
|
223
|
+
resume without renormalizing or reevaluating approval. `edit_file` declares
|
|
224
|
+
its existing alias and string-boolean tolerance through that seam and rejects
|
|
225
|
+
alias collisions. Provider replay substitutes `{}` only as the wire placeholder
|
|
226
|
+
for an already rejected call, paired with its error result. Direct `Tool#call`
|
|
227
|
+
remains a trusted host path and skips Agent validation. Task output now uses
|
|
228
|
+
the same JSON resource limits. This is minor-release behavior for hosts whose
|
|
229
|
+
policy or handlers previously received malformed values, whose custom
|
|
230
|
+
validators relied on implicit full-schema authority, or whose code mutated or
|
|
231
|
+
indexed a Tool's raw schema with symbol keys. `Schema.strict` results are now
|
|
232
|
+
deeply frozen, and `Tool.define` raises when both `input_schema:` and `schema:`
|
|
233
|
+
are supplied instead of silently choosing one. Definition compilation is paid
|
|
234
|
+
once; a completed call adds one bounded ownership pass and one compiled schema
|
|
235
|
+
traversal, plus ownership only when a normalizer replaces the value. Streaming
|
|
236
|
+
tool snapshots deep-freeze each newly refreshed bounded preview once; deltas
|
|
237
|
+
that reuse the cached preview are O(1), and pairing strings are copied.
|
|
238
|
+
Completed call envelope verification is one linear pass over that turn's
|
|
239
|
+
calls. Run and resume add one linear control-state read of the session log to
|
|
240
|
+
seed O(1) identity checks. Encoded Anthropic/OpenAI arguments and task text add
|
|
241
|
+
one linear lexical scan before JSON parsing; it is off the token-delta path. A
|
|
242
|
+
normalized approval adds only a small provenance marker. Structured
|
|
243
|
+
output request building adds one bounded schema ownership pass; task plans
|
|
244
|
+
compile their local validator once, with no added work on token streaming.
|
|
245
|
+
Arguments are now deeply frozen before hooks and handlers; hosts that mutated
|
|
246
|
+
model-supplied hashes or arrays must copy them first.
|
|
247
|
+
- Provider replay now keeps opaque state with its provider of origin. Gemini
|
|
248
|
+
sends tool schemas through `parametersJsonSchema`, preserves each signed part
|
|
249
|
+
boundary, and returns Google's function-call ID exactly when one was supplied;
|
|
250
|
+
legacy calls without a wire ID get a collision-safe internal ID without
|
|
251
|
+
mutating the signed provider part. On pre-3 Gemini turns, an earlier gated
|
|
252
|
+
same-name/no-ID call is rejected in band if a later sibling would answer
|
|
253
|
+
first; Gemini 3 wire IDs retain out-of-order pairing. A MAX_TOKENS turn never
|
|
254
|
+
executes a returned call. OpenAI treats a missing completed `arguments` field
|
|
255
|
+
as malformed instead of executing `{}`. Anthropic and OpenAI no longer place
|
|
256
|
+
another provider's signatures on their wire. These replay checks add constant
|
|
257
|
+
metadata work per completed block. Terminal records now fence later known
|
|
258
|
+
content for all three providers. Anthropic enforces
|
|
259
|
+
sequential and open content-block indexes plus the delta kind expected by the
|
|
260
|
+
open block. OpenAI enforces one open output item, its kind, any present item or
|
|
261
|
+
output-index correlation, and terminal event/status agreement. Unknown future
|
|
262
|
+
event types remain ignorable. The checks add only constant primitive
|
|
263
|
+
comparisons per known delta, with no parsing, copying, or I/O.
|
|
264
|
+
Completed foreign tool exchanges project to neutral text when a session
|
|
265
|
+
moves to Gemini, preserving the result without manufacturing a Gemini
|
|
266
|
+
function call that lacks Google's thought signature.
|
|
267
|
+
Real providers now emit the same initial `:start` snapshot as the Fake, and
|
|
268
|
+
every opened text, thinking, or tool-call block emits its matching end before
|
|
269
|
+
a terminal failure. Interrupted tool calls remain explicitly incomplete and
|
|
270
|
+
are stripped before Agent persistence; interrupted Anthropic thinking never
|
|
271
|
+
replays a partial signature.
|
|
272
|
+
- Tool execution now emits `:tool_started` when each resolved call commits to
|
|
273
|
+
invocation and carries a structured failure fact end to end. A handler may
|
|
274
|
+
return `ToolResult(error: true)`; unknown tools, policy blocks,
|
|
275
|
+
handler exceptions, timeouts, pre-invocation interruptions, failed result
|
|
276
|
+
hooks, healed dangling calls, and MCP `isError` results set it automatically.
|
|
277
|
+
The flag is sticky through `after_tool` rewrites, so changing error text
|
|
278
|
+
cannot relabel a failed operation as successful. `Event#tool_error` is always
|
|
279
|
+
true or false on `:tool_result`; new tool messages persist the same value, and
|
|
280
|
+
legacy messages without it remain unknown and replay with the historical
|
|
281
|
+
unmarked provider shape. Human approval denial remains a normal control
|
|
282
|
+
outcome rather than an execution error. Anthropic receives `is_error: true`;
|
|
283
|
+
Gemini receives the documented `error` function-response key; OpenAI retains
|
|
284
|
+
textual output because its function-call output has no
|
|
285
|
+
execution-error member. Error text is not classified by prefix, and an error
|
|
286
|
+
bit never authorizes a mechanical replay because a side effect may already
|
|
287
|
+
have happened; the model may still choose a new call, so hosts retain
|
|
288
|
+
idempotency and reconciliation policy. `ends_turn` remains the host's stronger
|
|
289
|
+
floor-transfer policy even when a result is errored; `handed_off?` does not
|
|
290
|
+
claim confirmed tool success. The new lifecycle signal adds one synchronous
|
|
291
|
+
sink event per committed call. Partial streaming Event construction adds two
|
|
292
|
+
primitive checks and one immutable nil slot; partial Message construction
|
|
293
|
+
adds one nil check and one immutable nil slot, with no parsing, buffering,
|
|
294
|
+
I/O, or string work. Tool batches add one sentinel write at commitment and one
|
|
295
|
+
identity check per result so a hard worker exit distinguishes started calls
|
|
296
|
+
from untouched queue entries; same-batch ordering adds one short-lived
|
|
297
|
+
identity map and a linear post-join pass, plus a second identity map only when
|
|
298
|
+
`after_tool` is configured;
|
|
299
|
+
starts arrive on serialized worker-thread callbacks, while same-batch results
|
|
300
|
+
still emit in model-call order after the batch joins. `:tool_started` and
|
|
301
|
+
handler-progress subscriber exceptions propagate and never become tool
|
|
302
|
+
failures.
|
|
303
|
+
Unknown, blocked, denied, queued, and pre-invocation interrupted calls add no
|
|
304
|
+
start event.
|
|
305
|
+
- A blockless nested object in the tool-schema DSL now declares a freeform JSON
|
|
306
|
+
object instead of leaking `LocalJumpError` during application boot. A bare
|
|
307
|
+
top-level `Schema.build` raises `ConfigurationError` with a useful message.
|
|
308
|
+
Constrained-output preparation now refuses this freeform shape, and directly
|
|
309
|
+
encountered explicitly open objects, with a schema path instead of silently
|
|
310
|
+
closing them and changing their meaning. Existing block-built and raw tool
|
|
311
|
+
schemas keep their wire shape. Tool-schema work remains definition-time;
|
|
312
|
+
constrained-output request building adds one openness check to its existing
|
|
313
|
+
property/item traversal, with no streaming or token-path work.
|
|
314
|
+
- Provider and MCP JSON bodies, individual SSE lines, and stdio JSON records
|
|
315
|
+
now default to an 8 MiB ceiling, configurable with `max_record_bytes:`.
|
|
316
|
+
Successful bodies are counted incrementally, declared oversized bodies fail
|
|
317
|
+
before reading, streaming remains unlimited across individually safe records,
|
|
318
|
+
and error responses retain at most a 500-byte valid UTF-8 preview. Requests
|
|
319
|
+
use identity encoding so compressed expansion cannot bypass the boundary.
|
|
320
|
+
Overflow closes the connection or child process; an oversized MCP
|
|
321
|
+
`tools/call` response remains explicitly ambiguous and is never replayed.
|
|
322
|
+
Stdio timeouts now cover the complete record instead of only its first byte;
|
|
323
|
+
any in-flight stdio wire failure after `tools/call` is ambiguous, reaps the
|
|
324
|
+
child, and requires a clean handshake before the next operation. Explicitly
|
|
325
|
+
closing and reusing an MCP client also clears its session, negotiated
|
|
326
|
+
protocol version, and server information before the fresh handshake.
|
|
327
|
+
Each completed SSE record also receives a bounded 20,001-token lexical
|
|
328
|
+
preflight, derived from the 10,000-node canonical limit, plus depth and 64 KiB
|
|
329
|
+
numeric-token ceilings before JSON parsing. A distinct
|
|
330
|
+
`ResponseTooComplexError` preserves the exact boundary and bounds Gemini
|
|
331
|
+
argument allocation inside its enclosing record. Either size or complexity
|
|
332
|
+
overflow resets an MCP wire and its negotiated session. An unconfirmed
|
|
333
|
+
`tools/call` outcome becomes ambiguous and is never replayed; a matching result
|
|
334
|
+
received before an invalid trailing SSE record remains authoritative, while
|
|
335
|
+
the next operation still performs a fresh handshake. Fragmented Anthropic and
|
|
336
|
+
OpenAI tool arguments also carry an aggregate 8 MiB cap across SSE records.
|
|
337
|
+
Their live partial preview stops reparsing after a bounded 64 KiB schedule
|
|
338
|
+
while raw delta events continue unchanged; OpenAI's completed item remains
|
|
339
|
+
authoritative. Fragmented Anthropic thinking
|
|
340
|
+
signatures stop immediately at the same aggregate ceiling rather than
|
|
341
|
+
growing quadratically or replaying a truncated opaque value.
|
|
342
|
+
The hot path adds one byte-count addition and comparison per JSON socket or SSE
|
|
343
|
+
line fragment, then one bounded linear lexical pass over each completed SSE
|
|
344
|
+
record before the provider's existing JSON parse.
|
|
345
|
+
- Remote MCP and server-side OAuth requests now default to public HTTPS and
|
|
346
|
+
direct connections. Every DNS answer is checked against the IANA
|
|
347
|
+
special-purpose registries, including embedded NAT64 destinations; one unsafe
|
|
348
|
+
answer rejects the whole set. Approved addresses are pinned without changing
|
|
349
|
+
Host, SNI, or certificate identity, alternates are tried only before request
|
|
350
|
+
transmission, and every connection cycle resolves and validates a fresh DNS
|
|
351
|
+
set, including Net::HTTP's internal reconnect cycles. Redirects and ambient
|
|
352
|
+
proxies cannot bypass the boundary. `allow_non_public:` is a narrow host
|
|
353
|
+
callback for approved internal HTTPS and explicit loopback development.
|
|
354
|
+
Client construction remains DNS-free; a reused socket adds no request-path
|
|
355
|
+
work. A new connection cycle pays one validated DNS lookup, with approved
|
|
356
|
+
candidates tried only before request transmission.
|
|
357
|
+
- MCP custom headers are copied at client construction and transport-owned
|
|
358
|
+
headers are rejected; a token callable remains the dynamic authentication
|
|
359
|
+
path. Tool discovery rejects repeated cursors and defaults to at most 100
|
|
360
|
+
pages and 10,000 tools; hosts can adjust either ceiling.
|
|
361
|
+
- MCP OAuth discovery now follows the required protected-resource and
|
|
362
|
+
authorization-server candidate order, uses exact candidate-aware resource and
|
|
363
|
+
issuer binding, honors challenge scope, requires S256 PKCE, and never adds
|
|
364
|
+
`offline_access` as gem policy. OAuth response bodies are uncompressed and
|
|
365
|
+
bounded to 256 KiB; token error descriptions cannot reflect credentials into
|
|
366
|
+
exceptions. A pre-registered client now requires its exact trusted `issuer:`,
|
|
367
|
+
and completion and refresh rediscover the token endpoint from that persisted
|
|
368
|
+
issuer instead of trusting a stored endpoint. New confidential flows persist
|
|
369
|
+
an explicit authentication method; legacy rows with a secret and no method
|
|
370
|
+
retain their previous `client_secret_post` behavior.
|
|
371
|
+
- The MCP Rails generator now persists `issuer` instead of `token_endpoint` and
|
|
372
|
+
exposes `mcp_allow_non_public` as the host's shared egress-policy hook. Existing
|
|
373
|
+
generated models must add and pass an issuer, restart pending OAuth flows,
|
|
374
|
+
and thread their non-public policy through the client and all OAuth
|
|
375
|
+
operations. Existing connected rows must reconnect unless the application
|
|
376
|
+
independently recorded their exact historical issuer. An issuer must never
|
|
377
|
+
be inferred from a token endpoint.
|
|
378
|
+
- GPT-5.6 Sol, Terra, and Luna are catalogued with their 1.05M-token context,
|
|
379
|
+
128K-token output ceiling, and standard paid pricing above and below the
|
|
380
|
+
272K long-context boundary. The `gpt-5.6` alias resolves to Sol. OpenAI usage
|
|
381
|
+
now separates billable GPT-5.6 cache writes from uncached input, keeping
|
|
382
|
+
compaction, reported cost, and cost budgets accurate.
|
|
383
|
+
- Directory workspaces reject existing symlinks in model-controlled paths and
|
|
384
|
+
omit symlinked files from listings. In a stable, host-controlled tree, reads,
|
|
385
|
+
writes, and deletes no longer follow a link outside the configured root.
|
|
386
|
+
- Streamable HTTP no longer replays an MCP `tools/call` when its response cannot
|
|
387
|
+
be confirmed. The server may already have committed the tool's side effect,
|
|
388
|
+
so the call now raises `AmbiguousDeliveryError` with an explicit do-not-retry
|
|
389
|
+
warning instead of transparently executing twice.
|
|
390
|
+
- Refusals and content-filter stops surface honestly on every provider
|
|
391
|
+
instead of reading as clean stops or retryable truncations. Gemini's
|
|
392
|
+
verdict finish reasons (SAFETY, RECITATION, LANGUAGE, BLOCKLIST,
|
|
393
|
+
PROHIBITED_CONTENT, SPII, the image verdicts) and blocked prompts
|
|
394
|
+
(promptFeedback.blockReason, which previously read as a retryable
|
|
395
|
+
truncated stream) fail fast as Mistri::InvalidRequestError with the
|
|
396
|
+
wire word in the message. MISSING_THOUGHT_SIGNATURE is a deterministic input
|
|
397
|
+
failure; its model fumbles and incomplete sentinels
|
|
398
|
+
(MALFORMED_FUNCTION_CALL, UNEXPECTED_TOOL_CALL, TOO_MANY_TOOL_CALLS,
|
|
399
|
+
MALFORMED_RESPONSE, FINISH_REASON_UNSPECIFIED, NO_IMAGE, and the catch-all
|
|
400
|
+
OTHER pair, documented as unknown reasons rather than rulings) error retryably.
|
|
401
|
+
Anthropic's refusal
|
|
402
|
+
stop reason fails fast carrying stop_details' category and explanation
|
|
403
|
+
(the API's guidance is a different model, never a same-model retry),
|
|
404
|
+
and model_context_window_exceeded maps to :length per its guidance.
|
|
405
|
+
OpenAI's incomplete_details content_filter and structured-output refusal
|
|
406
|
+
parts or deltas fail fast instead of reading as a completed answer; unknown
|
|
407
|
+
incomplete reasons retry rather than becoming clean stops. Partial content
|
|
408
|
+
stays on the errored message for hosts to show. Genuinely undocumented future
|
|
409
|
+
Gemini stop reasons still tolerate as clean stops, unchanged. Anthropic and
|
|
410
|
+
OpenAI top-level stream errors now classify documented authentication,
|
|
411
|
+
request/policy, rate-limit, timeout, overload, and server failures by their
|
|
412
|
+
wire code rather than retrying deterministic 4xx errors.
|
|
413
|
+
- Active session execution is not yet leased. Store appends from approvals,
|
|
414
|
+
steers, and reports remain concurrency-safe, but hosts must allow only one
|
|
415
|
+
`run` or `resume` at a time for a session. Even with one runner, a crash,
|
|
416
|
+
result-store failure, or subscriber exception after invocation but before
|
|
417
|
+
result persistence can leave an approved call open for possible duplicate
|
|
418
|
+
re-execution. Simultaneous runners widen the same risk. Hosts must use
|
|
419
|
+
idempotency/reconciliation across that commit gap; durable atomic claiming is
|
|
420
|
+
the next synchronization feature, not simulated by a local mutex.
|
|
421
|
+
- Sinks::Coalesced is origin-aware and thread-safe: a background worker's
|
|
422
|
+
deltas no longer merge into the parent's (or another worker's) event at
|
|
423
|
+
the same content index, and concurrent emitters serialize on an
|
|
424
|
+
internal mutex instead of racing the buffer. One uncontended mutex
|
|
425
|
+
acquire per event, at coalesced rate; nothing changes on the token
|
|
426
|
+
path.
|
|
427
|
+
- OpenAI response.failed events classify by their error code instead of
|
|
428
|
+
all reading as a retryable truncated stream: rate limits and server
|
|
429
|
+
errors stay retryable, timeouts retry, and permanent rejections
|
|
430
|
+
(invalid_prompt, the image family) carry the new
|
|
431
|
+
Mistri::InvalidRequestError shape and fail fast, so the loop no longer
|
|
432
|
+
burns its retry budget on input the provider already ruled out. A
|
|
433
|
+
failed response without an error object now errors instead of reading
|
|
434
|
+
as a clean stop.
|
|
435
|
+
- The model catalog carries each model's published standard paid direct-API
|
|
436
|
+
prices. Assemblers price every turn's usage from them: message.usage.cost and
|
|
437
|
+
Result#usage now report list-price dollar estimates for catalogued models, and the
|
|
438
|
+
Budget cost_usd ceiling actually stops a run. It never could before:
|
|
439
|
+
nothing computed cost, so the comparison always saw zero. Pricing is
|
|
440
|
+
selected per request, including GPT-5.4/5.5 and Gemini Pro long-context
|
|
441
|
+
tiers and Sonnet 5's September 2026 rate change. Unpriced usage is marked
|
|
442
|
+
unknown instead of free. A cost ceiling rejects an unknown model or origin
|
|
443
|
+
at construction and fails closed when a request's pricing is unknown at
|
|
444
|
+
runtime. Catalog pricing requires an explicit `catalog_pricing:` opt-in on
|
|
445
|
+
custom origins and observes service-tier policy without changing it;
|
|
446
|
+
nonstandard or unreported tiers stay unknown. A deterministic standard tier
|
|
447
|
+
is required up front for cost-budgeted Anthropic and OpenAI agents; Gemini's
|
|
448
|
+
reported standard, Flex, or Priority tier is honored rather than assuming
|
|
449
|
+
standard. Cost ceilings remain soft and are checked between model-visible
|
|
450
|
+
turns. An unmetered attempt raises Mistri::BudgetError instead of retrying
|
|
451
|
+
under false certainty; the error and an unpriced_attempt session entry retain
|
|
452
|
+
its partial accounting. Run usage includes every retry and compaction attempt,
|
|
453
|
+
and retry session entries now carry their own usage. Truncated streams preserve
|
|
454
|
+
partial token counts but mark their dollar total unknown.
|
|
455
|
+
- Compaction now uses the published 1M context windows for catalogued Fable,
|
|
456
|
+
Opus, and Sonnet models and the 1.05M windows for GPT-5.4 and GPT-5.5.
|
|
457
|
+
Automatic headroom protects a full next output plus framing slack when the
|
|
458
|
+
provider shares input and output capacity; Gemini's separately published
|
|
459
|
+
input limit keeps input-only headroom. An explicit `reserve:` still wins as
|
|
460
|
+
host policy.
|
|
461
|
+
`Compaction#automatic_reserve?` lets hosts distinguish the default mode from
|
|
462
|
+
an explicit 16,384-token policy without changing `Compaction#reserve`.
|
|
463
|
+
Usage reported before the latest compaction no longer makes a fresh summary
|
|
464
|
+
appear full. A single run can compact repeatedly across tool turns while
|
|
465
|
+
keeping parallel calls paired with every result. Large tool results are
|
|
466
|
+
bounded only on the lossy summarizer wire, with their beginning and end
|
|
467
|
+
retained. That shortening never mutates the stored result or an ordinary
|
|
468
|
+
request that still replays it. Compacted replay intentionally remains
|
|
469
|
+
summary plus tail. The live
|
|
470
|
+
provider harness now proves two compactions, an exact tool-token handoff,
|
|
471
|
+
and final fact recall inside one run.
|
|
472
|
+
|
|
6
473
|
## [0.5.0] - 2026-07-08
|
|
7
474
|
|
|
8
475
|
- The children registry: Session#children lists every sub-agent a session
|
|
@@ -46,7 +513,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
|
46
513
|
requires inline mode, enforced in band.
|
|
47
514
|
|
|
48
515
|
- Report delivery: a background child's terminal outcome reports back to
|
|
49
|
-
its parent
|
|
516
|
+
its parent. The report queues in the parent's inbox as a
|
|
50
517
|
typed subagent_report entry and folds at the next turn boundary the way
|
|
51
518
|
a steer does (the model sees `[Magpie finished] <report>`; failures
|
|
52
519
|
carry the error, stops say so), and a report landing as a run finishes
|
|
@@ -54,9 +521,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
|
54
521
|
event (agent, session_id, status, content) closes the child's lane in
|
|
55
522
|
whatever UI watched the spawn. Session#pending_inbox is the
|
|
56
523
|
steers-and-reports view (hosts that wake idle sessions on steers should
|
|
57
|
-
watch it instead); Session#deliver_report
|
|
58
|
-
|
|
59
|
-
-
|
|
524
|
+
watch it instead); Session#deliver_report drops sequential duplicate
|
|
525
|
+
delivery by child Session ID. Concurrent callers need serialization.
|
|
526
|
+
- With a lock adapter, dispatched runs use the child's lease before the
|
|
60
527
|
run-or-not decision: a queue that redelivers a live job leaves the
|
|
61
528
|
owner alone, a retry of a finished or cancelled child is a clean no-op,
|
|
62
529
|
and a retry of a child a crashed process left mid-run runs it again
|
|
@@ -415,4 +882,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
|
415
882
|
|
|
416
883
|
- Reserved the gem name.
|
|
417
884
|
|
|
885
|
+
[Unreleased]: https://github.com/mcheemaa/mistri/compare/v0.6.1...HEAD
|
|
886
|
+
[0.6.1]: https://github.com/mcheemaa/mistri/compare/v0.6.0...v0.6.1
|
|
887
|
+
[0.6.0]: https://github.com/mcheemaa/mistri/compare/v0.5.0...v0.6.0
|
|
888
|
+
[0.5.0]: https://github.com/mcheemaa/mistri/compare/v0.4.1...v0.5.0
|
|
889
|
+
[0.4.1]: https://github.com/mcheemaa/mistri/compare/v0.4.0...v0.4.1
|
|
890
|
+
[0.4.0]: https://github.com/mcheemaa/mistri/compare/v0.3.0...v0.4.0
|
|
891
|
+
[0.3.0]: https://github.com/mcheemaa/mistri/compare/v0.2.1...v0.3.0
|
|
892
|
+
[0.2.1]: https://github.com/mcheemaa/mistri/compare/v0.2.0...v0.2.1
|
|
893
|
+
[0.2.0]: https://github.com/mcheemaa/mistri/compare/v0.1.0...v0.2.0
|
|
418
894
|
[0.1.0]: https://github.com/mcheemaa/mistri/releases/tag/v0.1.0
|
data/CONTRIBUTING.md
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
Thanks for wanting to make mistri better.
|
|
4
|
+
|
|
5
|
+
## Setup
|
|
6
|
+
|
|
7
|
+
```console
|
|
8
|
+
$ bundle install
|
|
9
|
+
$ bundle exec rake test # hermetic, fast, no keys needed
|
|
10
|
+
$ bundle exec rubocop
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
The live suites need provider keys in a gitignored `.env.development.local`
|
|
14
|
+
(`ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, `GEMINI_API_KEY`):
|
|
15
|
+
|
|
16
|
+
```console
|
|
17
|
+
$ MISTRI_LIVE=1 bundle exec rake test # gated live tests
|
|
18
|
+
$ bundle exec rake integration # critical end-to-end scenarios, per model
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Missing keys skip their provider. Read the output before treating a live run as
|
|
22
|
+
three-provider coverage.
|
|
23
|
+
|
|
24
|
+
## The rules that keep the gem what it is
|
|
25
|
+
|
|
26
|
+
- **Zero runtime dependencies.** Development and test dependencies are fine;
|
|
27
|
+
a runtime dependency needs an unusually good reason.
|
|
28
|
+
- **Tests ship with the change**, in the same commit. New provider-touching
|
|
29
|
+
behavior gets a live test.
|
|
30
|
+
- **Errors follow their boundary.** A tool failure becomes a tool result the
|
|
31
|
+
model can react to. Built-in provider failures finish as an errored Result;
|
|
32
|
+
direct MCP Client and configuration failures raise. A bridged MCP Client
|
|
33
|
+
failure crosses the ordinary tool-result boundary.
|
|
34
|
+
- **Sessions are append-only.** Derive state from the entry log; never
|
|
35
|
+
require a repair step after a crash.
|
|
36
|
+
- **Comments say why, not what.** Few and load-bearing.
|
|
37
|
+
- Run `bundle exec rubocop` before pushing; CI enforces it and a coverage
|
|
38
|
+
floor.
|
|
39
|
+
|
|
40
|
+
## Pull requests
|
|
41
|
+
|
|
42
|
+
Small, focused, sentence-case titles. Describe the why in a paragraph, not
|
|
43
|
+
a checklist. If the change alters public behavior, add a CHANGELOG entry
|
|
44
|
+
under Unreleased.
|
|
45
|
+
|
|
46
|
+
## Documentation
|
|
47
|
+
|
|
48
|
+
The README is the adoption path: category, first success, fit, core mechanism,
|
|
49
|
+
and honest reliability boundaries. Put detailed contracts in the task-oriented
|
|
50
|
+
Markdown guides under `docs/`, and put required host migrations in
|
|
51
|
+
`UPGRADING.md`. Link repository files relatively so links work on branches and
|
|
52
|
+
pull requests. Do not duplicate the changelog into a guide.
|