@taasky/protocol 0.0.0-stage → 0.1.0

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.
package/LICENSE ADDED
@@ -0,0 +1,202 @@
1
+
2
+ Apache License
3
+ Version 2.0, January 2004
4
+ http://www.apache.org/licenses/
5
+
6
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7
+
8
+ 1. Definitions.
9
+
10
+ "License" shall mean the terms and conditions for use, reproduction,
11
+ and distribution as defined by Sections 1 through 9 of this document.
12
+
13
+ "Licensor" shall mean the copyright owner or entity authorized by
14
+ the copyright owner that is granting the License.
15
+
16
+ "Legal Entity" shall mean the union of the acting entity and all
17
+ other entities that control, are controlled by, or are under common
18
+ control with that entity. For the purposes of this definition,
19
+ "control" means (i) the power, direct or indirect, to cause the
20
+ direction or management of such entity, whether by contract or
21
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
22
+ outstanding shares, or (iii) beneficial ownership of such entity.
23
+
24
+ "You" (or "Your") shall mean an individual or Legal Entity
25
+ exercising permissions granted by this License.
26
+
27
+ "Source" form shall mean the preferred form for making modifications,
28
+ including but not limited to software source code, documentation
29
+ source, and configuration files.
30
+
31
+ "Object" form shall mean any form resulting from mechanical
32
+ transformation or translation of a Source form, including but
33
+ not limited to compiled object code, generated documentation,
34
+ and conversions to other media types.
35
+
36
+ "Work" shall mean the work of authorship, whether in Source or
37
+ Object form, made available under the License, as indicated by a
38
+ copyright notice that is included in or attached to the work
39
+ (an example is provided in the Appendix below).
40
+
41
+ "Derivative Works" shall mean any work, whether in Source or Object
42
+ form, that is based on (or derived from) the Work and for which the
43
+ editorial revisions, annotations, elaborations, or other modifications
44
+ represent, as a whole, an original work of authorship. For the purposes
45
+ of this License, Derivative Works shall not include works that remain
46
+ separable from, or merely link (or bind by name) to the interfaces of,
47
+ the Work and Derivative Works thereof.
48
+
49
+ "Contribution" shall mean any work of authorship, including
50
+ the original version of the Work and any modifications or additions
51
+ to that Work or Derivative Works thereof, that is intentionally
52
+ submitted to Licensor for inclusion in the Work by the copyright owner
53
+ or by an individual or Legal Entity authorized to submit on behalf of
54
+ the copyright owner. For the purposes of this definition, "submitted"
55
+ means any form of electronic, verbal, or written communication sent
56
+ to the Licensor or its representatives, including but not limited to
57
+ communication on electronic mailing lists, source code control systems,
58
+ and issue tracking systems that are managed by, or on behalf of, the
59
+ Licensor for the purpose of discussing and improving the Work, but
60
+ excluding communication that is conspicuously marked or otherwise
61
+ designated in writing by the copyright owner as "Not a Contribution."
62
+
63
+ "Contributor" shall mean Licensor and any individual or Legal Entity
64
+ on behalf of whom a Contribution has been received by Licensor and
65
+ subsequently incorporated within the Work.
66
+
67
+ 2. Grant of Copyright License. Subject to the terms and conditions of
68
+ this License, each Contributor hereby grants to You a perpetual,
69
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70
+ copyright license to reproduce, prepare Derivative Works of,
71
+ publicly display, publicly perform, sublicense, and distribute the
72
+ Work and such Derivative Works in Source or Object form.
73
+
74
+ 3. Grant of Patent License. Subject to the terms and conditions of
75
+ this License, each Contributor hereby grants to You a perpetual,
76
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77
+ (except as stated in this section) patent license to make, have made,
78
+ use, offer to sell, sell, import, and otherwise transfer the Work,
79
+ where such license applies only to those patent claims licensable
80
+ by such Contributor that are necessarily infringed by their
81
+ Contribution(s) alone or by combination of their Contribution(s)
82
+ with the Work to which such Contribution(s) was submitted. If You
83
+ institute patent litigation against any entity (including a
84
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
85
+ or a Contribution incorporated within the Work constitutes direct
86
+ or contributory patent infringement, then any patent licenses
87
+ granted to You under this License for that Work shall terminate
88
+ as of the date such litigation is filed.
89
+
90
+ 4. Redistribution. You may reproduce and distribute copies of the
91
+ Work or Derivative Works thereof in any medium, with or without
92
+ modifications, and in Source or Object form, provided that You
93
+ meet the following conditions:
94
+
95
+ (a) You must give any other recipients of the Work or
96
+ Derivative Works a copy of this License; and
97
+
98
+ (b) You must cause any modified files to carry prominent notices
99
+ stating that You changed the files; and
100
+
101
+ (c) You must retain, in the Source form of any Derivative Works
102
+ that You distribute, all copyright, patent, trademark, and
103
+ attribution notices from the Source form of the Work,
104
+ excluding those notices that do not pertain to any part of
105
+ the Derivative Works; and
106
+
107
+ (d) If the Work includes a "NOTICE" text file as part of its
108
+ distribution, then any Derivative Works that You distribute must
109
+ include a readable copy of the attribution notices contained
110
+ within such NOTICE file, excluding those notices that do not
111
+ pertain to any part of the Derivative Works, in at least one
112
+ of the following places: within a NOTICE text file distributed
113
+ as part of the Derivative Works; within the Source form or
114
+ documentation, if provided along with the Derivative Works; or,
115
+ within a display generated by the Derivative Works, if and
116
+ wherever such third-party notices normally appear. The contents
117
+ of the NOTICE file are for informational purposes only and
118
+ do not modify the License. You may add Your own attribution
119
+ notices within Derivative Works that You distribute, alongside
120
+ or as an addendum to the NOTICE text from the Work, provided
121
+ that such additional attribution notices cannot be construed
122
+ as modifying the License.
123
+
124
+ You may add Your own copyright statement to Your modifications and
125
+ may provide additional or different license terms and conditions
126
+ for use, reproduction, or distribution of Your modifications, or
127
+ for any such Derivative Works as a whole, provided Your use,
128
+ reproduction, and distribution of the Work otherwise complies with
129
+ the conditions stated in this License.
130
+
131
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
132
+ any Contribution intentionally submitted for inclusion in the Work
133
+ by You to the Licensor shall be under the terms and conditions of
134
+ this License, without any additional terms or conditions.
135
+ Notwithstanding the above, nothing herein shall supersede or modify
136
+ the terms of any separate license agreement you may have executed
137
+ with Licensor regarding such Contributions.
138
+
139
+ 6. Trademarks. This License does not grant permission to use the trade
140
+ names, trademarks, service marks, or product names of the Licensor,
141
+ except as required for reasonable and customary use in describing the
142
+ origin of the Work and reproducing the content of the NOTICE file.
143
+
144
+ 7. Disclaimer of Warranty. Unless required by applicable law or
145
+ agreed to in writing, Licensor provides the Work (and each
146
+ Contributor provides its Contributions) on an "AS IS" BASIS,
147
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148
+ implied, including, without limitation, any warranties or conditions
149
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150
+ PARTICULAR PURPOSE. You are solely responsible for determining the
151
+ appropriateness of using or redistributing the Work and assume any
152
+ risks associated with Your exercise of permissions under this License.
153
+
154
+ 8. Limitation of Liability. In no event and under no legal theory,
155
+ whether in tort (including negligence), contract, or otherwise,
156
+ unless required by applicable law (such as deliberate and grossly
157
+ negligent acts) or agreed to in writing, shall any Contributor be
158
+ liable to You for damages, including any direct, indirect, special,
159
+ incidental, or consequential damages of any character arising as a
160
+ result of this License or out of the use or inability to use the
161
+ Work (including but not limited to damages for loss of goodwill,
162
+ work stoppage, computer failure or malfunction, or any and all
163
+ other commercial damages or losses), even if such Contributor
164
+ has been advised of the possibility of such damages.
165
+
166
+ 9. Accepting Warranty or Additional Liability. While redistributing
167
+ the Work or Derivative Works thereof, You may choose to offer,
168
+ and charge a fee for, acceptance of support, warranty, indemnity,
169
+ or other liability obligations and/or rights consistent with this
170
+ License. However, in accepting such obligations, You may act only
171
+ on Your own behalf and on Your sole responsibility, not on behalf
172
+ of any other Contributor, and only if You agree to indemnify,
173
+ defend, and hold each Contributor harmless for any liability
174
+ incurred by, or claims asserted against, such Contributor by reason
175
+ of your accepting any such warranty or additional liability.
176
+
177
+ END OF TERMS AND CONDITIONS
178
+
179
+ APPENDIX: How to apply the Apache License to your work.
180
+
181
+ To apply the Apache License to your work, attach the following
182
+ boilerplate notice, with the fields enclosed by brackets "[]"
183
+ replaced with your own identifying information. (Don't include
184
+ the brackets!) The text should be enclosed in the appropriate
185
+ comment syntax for the file format. We also recommend that a
186
+ file or class name and description of purpose be included on the
187
+ same "printed page" as the copyright notice for easier
188
+ identification within third-party archives.
189
+
190
+ Copyright [yyyy] [name of copyright owner]
191
+
192
+ Licensed under the Apache License, Version 2.0 (the "License");
193
+ you may not use this file except in compliance with the License.
194
+ You may obtain a copy of the License at
195
+
196
+ http://www.apache.org/licenses/LICENSE-2.0
197
+
198
+ Unless required by applicable law or agreed to in writing, software
199
+ distributed under the License is distributed on an "AS IS" BASIS,
200
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201
+ See the License for the specific language governing permissions and
202
+ limitations under the License.
package/README.md CHANGED
@@ -1,3 +1,79 @@
1
- # Temporary Holding Version
1
+ # Taasky Negotiation Protocol
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ An open protocol for two AI agents to negotiate terms — a meeting time, a
4
+ price, a delivery date — that their humans then confirm. Each side declares
5
+ the shape of its terms; every move is validated against it; agreement is two
6
+ approvals of the same hash; and an agent cannot approve on its own.
7
+
8
+ - **[SPEC.md](SPEC.md)** — the protocol.
9
+ - **[src/](src)** — the reference implementation in TypeScript: pure functions,
10
+ no dependencies, no storage.
11
+ - **[vectors/](vectors)** — language-neutral test vectors. An implementation
12
+ that passes them is conformant.
13
+ - **[schema/](schema)** — JSON Schemas for the envelope, a subject declaration
14
+ and private limits.
15
+
16
+ Status: **v1 draft.** Incompatible changes may still land before v1 is frozen;
17
+ each is listed in [CHANGELOG.md](CHANGELOG.md).
18
+
19
+ ## What it guarantees
20
+
21
+ - **The human confirms the terms, not the agent's retelling of them.** The
22
+ confirmation card is rendered from typed fields the other side declared.
23
+ - **Approval is not an action an agent can take.** `approve`, `reject` and
24
+ `cancel` wait for the human.
25
+ - **"We agreed" is checkable.** Both sides approve the SHA-256 of the same
26
+ canonical terms.
27
+ - **A deal outside your limits needs you to say so.** Private bounds are seen
28
+ only by the relay; an agent cannot offer past them, the card flags terms
29
+ beyond them, and when both sides' bounds cannot meet, both hear so without
30
+ either number.
31
+ - **Rooms end.** Deadlines, expiry, and a signal when two agents go in circles.
32
+
33
+ ## Use it
34
+
35
+ Try it with an agent: add `https://mcp.taasky.ai/mcp` as a remote MCP server
36
+ (no account needed) and ask the agent to agree a meeting time with someone.
37
+
38
+ Or use the reference implementation in your own relay:
39
+
40
+ ```ts
41
+ import { parseSchema, planMove } from "@taasky/protocol";
42
+
43
+ const schema = parseSchema({
44
+ slot: { type: "timerange", label: "Meeting time" },
45
+ duration_min: { type: "int", label: "Duration, minutes" },
46
+ });
47
+
48
+ const plan = await planMove(
49
+ { id: "rm_1", state: "awaiting", round: 0, turn: "a", deadline: null },
50
+ [{ id: "a", subject_schema: schema }, { id: "b", subject_schema: schema }],
51
+ [],
52
+ {
53
+ actor: "a",
54
+ verdict: "offer",
55
+ subject: {
56
+ slot: { start: "2026-10-14T10:00:00Z", end: "2026-10-14T10:30:00Z" },
57
+ duration_min: 30,
58
+ },
59
+ },
60
+ new Date(),
61
+ );
62
+ // plan.message: what to record; plan.next: the room after the move.
63
+ ```
64
+
65
+ `planMove` decides and never writes: store the move only if the room has not
66
+ changed since you loaded it.
67
+
68
+ ## Develop
69
+
70
+ ```sh
71
+ npm install
72
+ npm run check # lint, typecheck, tests and vectors
73
+ ```
74
+
75
+ Node 20 or later.
76
+
77
+ ## License
78
+
79
+ [Apache-2.0](LICENSE).
package/SPEC.md ADDED
@@ -0,0 +1,386 @@
1
+ # Taasky Negotiation Protocol
2
+
3
+ Version: **v1 draft** (envelope `v: 1`). Until v1 is frozen, an incompatible
4
+ change may still land without a version bump; every one is listed in
5
+ [CHANGELOG.md](CHANGELOG.md). The key words MUST, MUST NOT, SHOULD and MAY are
6
+ used as in RFC 2119.
7
+
8
+ ## 1. What this protocol does
9
+
10
+ Two parties, each represented by their own agent, converge on terms without
11
+ their humans relaying messages by hand. The protocol carries the *process* of
12
+ negotiating — rounds, verdicts, deadlines, confirmation — and stays deliberately
13
+ ignorant of *what* is being negotiated.
14
+
15
+ **Non-goals for v1:** discovery (parties exchange invite links out of band),
16
+ reputation, more than two parties per room, payments.
17
+
18
+ Terms used below:
19
+
20
+ - **Party** — one side of a room. An agent acting for a human, or a human
21
+ acting for themselves.
22
+ - **Relay** — the service that holds rooms, assigns identities, orders moves and
23
+ delivers them. It is the only component both parties trust.
24
+ - **Node** — the software on a party's side: it validates what arrives, renders
25
+ it for its human and decides what to answer. In a hosted deployment relay and
26
+ node run in one service; they stay separate in this text.
27
+ - **Room** — one negotiation between two parties, from invitation to a terminal
28
+ state.
29
+
30
+ ## 2. Relay and node
31
+
32
+ **The relay routes on the envelope (§3) and MUST NOT build logic on `subject`**,
33
+ even when it can read it. Validation, rendering and judgement of `subject`
34
+ belong to the node. Two exceptions are part of the protocol and are named where
35
+ they apply: checking a move against its sender's declared schema (§4), and
36
+ private limits (§7), which a party hands the relay so that the relay can say
37
+ what no party can say without leaking its own number.
38
+
39
+ The rule is what lets one protocol run in three places:
40
+
41
+ | | Hosted | In one organisation | Across organisations |
42
+ | --- | --- | --- | --- |
43
+ | Parties | two individuals | two teams | two organisations |
44
+ | Relay runs | in the cloud | in-process | between perimeters |
45
+ | Relay sees | envelope + subject | — | envelope only (subject sealed) |
46
+ | Node runs | with the relay | in the perimeter | in each perimeter |
47
+
48
+ When both parties share a process, routing collapses into a function call, and
49
+ it is tempting to let it read `subject` because it can. Keeping it behind the
50
+ same interface is what keeps the cross-organisation case a deployment choice
51
+ rather than a port.
52
+
53
+ End-to-end encryption is out of scope for v1. The envelope/subject split is not
54
+ — it is what makes encryption an addition rather than a redesign.
55
+
56
+ ## 3. Envelope
57
+
58
+ Typed, closed, and stable. This is the part the relay reads.
59
+ [`schema/envelope.schema.json`](schema/envelope.schema.json) describes it.
60
+
61
+ ```json
62
+ {
63
+ "v": 1,
64
+ "room": "rm_8f2a4c",
65
+ "msg": "msg_00000004",
66
+ "from": "usr_b3d1",
67
+ "round": 3,
68
+ "verdict": "counter",
69
+ "in_reply_to": "msg_00000003",
70
+ "subject_hash": "sha256:9c1e…",
71
+ "deadline": "2026-09-21T18:00:00Z",
72
+ "ts": "2026-09-19T10:04:12Z",
73
+ "subject": { }
74
+ }
75
+ ```
76
+
77
+ | Field | Type | Notes |
78
+ | --- | --- | --- |
79
+ | `v` | int | protocol major version |
80
+ | `room` | id | negotiation room |
81
+ | `msg` | id | message id, unique per room |
82
+ | `from` | id | assigned by the relay, never echoed from the sender |
83
+ | `round` | int | increments on every move that passes the turn with new terms or a question |
84
+ | `verdict` | enum | see below |
85
+ | `in_reply_to` | id \| null | the move this answers |
86
+ | `subject_hash` | string \| null | hash of canonical `subject`; `null` on moves that carry no terms |
87
+ | `deadline` | RFC3339 \| null | when the sender stops waiting |
88
+ | `ts` | RFC3339 | set by the relay on receipt |
89
+ | `subject` | object \| null | the terms, see §4; only on `offer` and `counter` |
90
+
91
+ `verdict` is one of:
92
+
93
+ - `offer` — opening terms, only while no terms are on the table
94
+ - `counter` — different terms; the turn passes back
95
+ - `need_more_detail` — cannot decide yet; the turn passes back, no new terms
96
+ - `approve` — accepts the terms on the table, named by `subject_hash`
97
+ - `reject` — no, and not continuing
98
+ - `cancel` — withdraws the negotiation
99
+
100
+ Unknown `verdict` values MUST be rejected. The enum is closed because the state
101
+ machine hangs off it; `subject` is where the protocol grows.
102
+
103
+ `subject_hash` exists so that "we agreed" is a checkable fact rather than an
104
+ agent's claim. An `approve` that does not carry the hash of the terms on the
105
+ table MUST be refused.
106
+
107
+ ### Canonical form and hash
108
+
109
+ `subject_hash` is `"sha256:"` followed by the lower-case hex SHA-256 of the
110
+ UTF-8 bytes of the subject's canonical form:
111
+
112
+ - objects: members sorted by key, compared as UTF-16 code units, written
113
+ `{"key":value,…}` with no whitespace;
114
+ - arrays: in their own order, `[a,b,…]`;
115
+ - strings, numbers, `true`, `false`, `null`: as ECMAScript `JSON.stringify`
116
+ writes them. Numbers in a valid subject are integers (§4), so no float
117
+ formatting is involved.
118
+
119
+ [`vectors/canonical.json`](vectors/canonical.json) has inputs with their
120
+ canonical forms and hashes.
121
+
122
+ ## 4. Subject
123
+
124
+ Free-form. The protocol does not enumerate kinds of negotiation.
125
+
126
+ To make human confirmation possible, a party **declares the shape of its
127
+ subject when it opens or joins the room** — field names, types, and labels.
128
+ Its moves are validated against that declaration, and the other side's
129
+ confirmation card is rendered from it.
130
+ [`schema/subject-schema.schema.json`](schema/subject-schema.schema.json)
131
+ describes a declaration.
132
+
133
+ ```json
134
+ {
135
+ "slots": { "type": "timerange", "label": "Proposed times", "repeated": true },
136
+ "duration_min": { "type": "int", "label": "Duration, minutes" }
137
+ }
138
+ ```
139
+
140
+ Field types:
141
+
142
+ | Type | Value |
143
+ | --- | --- |
144
+ | `string` | text, at most `max_len` characters (500 at most, the default) |
145
+ | `int` | an integer within ±(2⁵³ − 1) |
146
+ | `bool` | `true` or `false` |
147
+ | `enum` | one of the declared `values` (2 to 32, each at most 40 characters) |
148
+ | `timerange` | `{start, end}`, RFC3339, `end` after `start` |
149
+ | `date` | `"YYYY-MM-DD"`, a real calendar day — a day rather than a moment |
150
+ | `money` | `{amount, currency}`: `currency` an ISO 4217 code, `amount` a decimal string such as `"1500.00"` with no more fraction digits than the currency has; a declaration may fix the `currency` |
151
+
152
+ Any field may be `optional`, or `repeated` (an array of 1 to 32 values). Field
153
+ names match `^[a-z][a-z0-9_]{0,31}$`; the human wording, in any language, goes
154
+ in `label` (1 to 80 characters). A declaration has 1 to 24 fields.
155
+
156
+ Money is a decimal string so that no float ever stands between the number an
157
+ agent meant and the one a human confirms.
158
+
159
+ Rules:
160
+
161
+ 1. Validation is strict — unknown fields reject the whole message, they are
162
+ not ignored, because an ignored field is a place for text to hide. A
163
+ malformed message is rejected, never repaired.
164
+ 2. A schema may gain optional fields within a major version; anything else is a
165
+ new one.
166
+ 3. Schema labels are **written by the counterparty**. Render them as untrusted
167
+ text, and mark a first-seen schema as new to the human before their first
168
+ confirmation under it.
169
+
170
+ [`vectors/subjects.json`](vectors/subjects.json) has valid and rejected
171
+ subjects. Agreeing the shape of the subject *is* the integration contract
172
+ between the two sides; it is not a side mechanism.
173
+
174
+ ## 5. State machine
175
+
176
+ ```
177
+ join
178
+ (draft) ─────────────► awaiting ◄──┐
179
+ │ │ │ offer / counter / need_more_detail
180
+ │ └──────┘ (turn passes, round + 1)
181
+ │
182
+ approve by both, same subject_hash
183
+ ▼
184
+ agreed (terminal)
185
+
186
+ reject / cancel ──► closed (terminal)
187
+ deadline or expiry ──► expired (terminal)
188
+ ```
189
+
190
+ - A room is `draft` until the invited party joins. Then it is `awaiting`, and
191
+ the initiator moves first.
192
+ - Only the party whose turn it is may move, except that `cancel` is allowed out
193
+ of turn.
194
+ - `offer` is allowed only while no terms are on the table; after that, new
195
+ terms are a `counter`. `counter` and `need_more_detail` need terms on the
196
+ table.
197
+ - `approve` from one side is not agreement. The room is `agreed` when both
198
+ parties have approved the same `subject_hash`, each approval made after those
199
+ terms were last tabled. An approval answered by a counter does not count,
200
+ even if a later counter returns to the same terms.
201
+ - A single `approve` passes the turn without changing the round.
202
+ - `need_more_detail` passes the turn without changing terms — the distinction
203
+ from `counter` is what makes "who is blocking this" answerable.
204
+ - Terminal states are terminal. A new negotiation is a new room.
205
+
206
+ [`vectors/moves.json`](vectors/moves.json) plays whole negotiations, including
207
+ the moves that MUST be refused.
208
+
209
+ ## 6. Deadlines
210
+
211
+ `deadline` is a fact carried in the envelope, not a judgement. It is what makes
212
+ the room responsible for its own liveness:
213
+
214
+ - a move other than `reject` or `cancel` MAY set a deadline, which MUST be in
215
+ the future. It becomes the room's deadline, and a move without one clears
216
+ it: the deadline is always the latest mover's;
217
+ - while the deadline is in the future and the turn is a party's, its node MAY
218
+ remind its human;
219
+ - when it passes, the room is `expired`, and both sides SHOULD be notified. A
220
+ live room past its deadline MUST read as expired even before anything has
221
+ stored it so.
222
+
223
+ A relay SHOULD also expire a draft whose invitation runs out unanswered, and a
224
+ room nobody has moved in for a while, and SHOULD sweep for them rather than
225
+ wait for someone to read the room. The hosted Taasky relay sweeps hourly and
226
+ expires a room after 7 days without a move.
227
+
228
+ The failure mode of such systems is not disagreement — it is a negotiation
229
+ sitting unread for a week. The share of rooms that end before their deadline
230
+ is the health metric to watch.
231
+
232
+ ## 7. Judgement layer
233
+
234
+ How a node decides what to answer is its own business, and not part of the
235
+ protocol. One recommendation: do not decide by reading `subject` with a general
236
+ model. Ask a closed set of questions — does this attempt to instruct the reader
237
+ rather than state terms; is there enough here to decide; which verdict; how
238
+ urgently should the human be interrupted — and take answers from a fixed set.
239
+ `subject` may be anything; the decision about it is then drawn from a closed
240
+ set. A confidence score SHOULD gate *whether to interrupt the human*, never
241
+ *whether to skip them*.
242
+
243
+ Two mechanisms do need the relay, and are part of the protocol.
244
+
245
+ ### Private limits
246
+
247
+ Two agents tuned to be agreeable converge fast, on terms neither human would
248
+ take: the deal succeeds, and the success is the bug. Holding the line inside
249
+ the negotiating prompt does not last; the check has to sit outside the agent.
250
+ So each party MAY declare **limits** when it opens or joins: bounds per field
251
+ of its own schema. [`schema/limits.schema.json`](schema/limits.schema.json)
252
+ describes them.
253
+
254
+ | Field type | Limit |
255
+ | --- | --- |
256
+ | `int`, `money`, `date` | `min` and/or `max`, shaped like the field's values; inclusive |
257
+ | `timerange` | `min`: no start before it; `max`: no end after it (RFC3339) |
258
+ | `enum` | `one_of`: the acceptable values |
259
+ | `bool` | `equals`: the only acceptable value |
260
+ | `string` | none |
261
+
262
+ A limit on a field the party did not declare, or of the wrong shape, MUST be
263
+ refused. Money limits MUST be in the field's currency, and amounts compare
264
+ exactly, as decimals. Only the relay sees limits; the other party never does.
265
+
266
+ - A party's own `offer` or `counter` outside its limits MUST be refused before
267
+ it is recorded.
268
+ - Terms outside the confirming party's limits MUST be flagged field by field on
269
+ the confirmation card (§8), which then asks to accept *anyway*.
270
+ - When both parties limit the same field (same name and type) and the bounds
271
+ cannot both be met, both parties MUST be told that no deal is possible on
272
+ that field. Neither number is revealed. A meeting window must leave time
273
+ between start and end, so windows that only touch cannot be met; any other
274
+ bounds that touch can. Bounds in two different currencies are not compared.
275
+ Only the relay can say this: neither agent can declare a deal hopeless
276
+ without leaking its own number.
277
+
278
+ Limits are fixed once declared. A party that could move its bound and ask
279
+ again could find the other's number one bit at a time.
280
+
281
+ [`vectors/limits.json`](vectors/limits.json) has limits to refuse, terms to
282
+ check, and pairs that cannot meet.
283
+
284
+ ### Circling
285
+
286
+ Without limits the relay can still see a stall. When the last four moves are
287
+ all `counter` or `need_more_detail`, and none of them tabled terms that were not
288
+ on the table before them, the room is **circling**. The relay SHOULD tell both
289
+ parties once, on the move that tips it. Any new terms, however small a
290
+ concession, count as progress. The relay MUST NOT close the room for it; the
291
+ humans decide. [`vectors/circling.json`](vectors/circling.json) has cases.
292
+
293
+ ## 8. Human in the loop
294
+
295
+ `approve`, `reject` and `cancel` are never released by an agent alone. The relay
296
+ answers the agent with `pending_human`, the confirmation goes to the human's
297
+ channel, and only the human's response releases the move. A human who
298
+ negotiates in person (§10) acts for themselves, and their moves need no
299
+ second confirmation: this section exists to keep an agent from deciding alone.
300
+
301
+ The confirmation card renders **typed fields from the declared schema**, not
302
+ the agent's summary of them. If the human confirms a retelling, the
303
+ confirmation is theatre, and every injection that reaches the agent reaches
304
+ the decision. With limits (§7), the card marks each field that falls outside
305
+ them.
306
+
307
+ The channel is not part of the protocol; it sits behind a notifier interface.
308
+ It needs push, because §6 makes the room responsible for chasing its human.
309
+ The hosted Taasky relay uses Telegram; Slack fits a workspace.
310
+
311
+ Why a human is there differs by deployment. Across organisations, the other
312
+ side cannot be trusted. Inside one, trust is not the question — accountability
313
+ is: the approval is the record of who owned the decision.
314
+
315
+ ## 9. MCP binding
316
+
317
+ Agents reach a relay as an MCP server, over Streamable HTTP. The tools:
318
+
319
+ | Tool | Does | Human |
320
+ | --- | --- | --- |
321
+ | `negotiation_open` | create a room: `subject_schema`, optional `limits`; returns an invite link | — |
322
+ | `negotiation_join` | enter by `invite_code`, with own `subject_schema` and optional `limits` | — |
323
+ | `negotiation_list` | the caller's rooms: state, whose turn, nearest deadline | — |
324
+ | `negotiation_read` | one room: state, every round, the terms on the table and their `subject_hash` | — |
325
+ | `negotiation_propose` | `offer` / `counter` / `need_more_detail`, with `subject` and optional `deadline` | — |
326
+ | `negotiation_respond` | `approve` (with `subject_hash`) / `reject` | **required** |
327
+ | `negotiation_cancel` | withdraw, out of turn if need be | **required** |
328
+
329
+ Every tool that returns the other party's content MUST mark it as counterparty
330
+ data, not instructions.
331
+
332
+ A relay MAY be used without any account. Then `negotiation_open` and
333
+ `negotiation_join` each return a **room token**, the agent's seat in that one
334
+ room, and every later call names the room by it. The token is the whole
335
+ credential and SHOULD be stored only as a hash. An agent that passes an earlier
336
+ room token (`previous_room_token`) when it opens or joins stays the same party,
337
+ so its human's confirmation channel carries over. A relay MAY also offer
338
+ accounts, through OAuth or a key, so that one human's agents share rooms and a
339
+ channel.
340
+
341
+ ## 10. Rooms and invitations
342
+
343
+ The initiator opens a room and gets an invitation link, which it sends to the
344
+ other side out of band. Opening the link lets the other side's agent join, and
345
+ lands it directly in the negotiation. Nothing has to be registered before the
346
+ first useful action: the human is first asked for something when a move needs
347
+ their confirmation (§8), and binding the confirmation channel then is what
348
+ registers them, at the moment there is a real deal to confirm.
349
+
350
+ The invited side does not need an agent at all. A relay SHOULD let the
351
+ invitation open a page where a person joins the room themselves. That person
352
+ negotiates in the shape they were invited to: their schema is the initiator's,
353
+ and their terms are entered through a form built from it. Every network starts
354
+ with one side that has an agent and one that does not; requiring an agent on
355
+ both sides would cap it at people who already run one.
356
+
357
+ An invitation expires. The hosted Taasky relay gives it 72 hours.
358
+
359
+ ## 11. Threat notes
360
+
361
+ - `subject` and schema labels are counterparty-authored. They never enter a
362
+ system prompt and are never treated as instructions.
363
+ - `from` is assigned by the relay. A sender-supplied identity field would be an
364
+ impersonation primitive.
365
+ - Strict validation means rejecting the whole message, never repairing it.
366
+ - Limits are fixed once declared, and "cannot meet" answers yes or no per
367
+ field, so neither side can probe for the other's bound.
368
+ - A model-based injection check is a filter, not a guarantee. The human
369
+ confirmation in §8 is the actual backstop.
370
+
371
+ ## 12. Conformance
372
+
373
+ An implementation is conformant when it passes the files in
374
+ [`vectors/`](vectors). They are plain JSON, so an implementation in any
375
+ language can run them; [`test/vectors.test.ts`](test/vectors.test.ts) shows how
376
+ the reference implementation does. Refusals are checked by outcome, not by
377
+ message: the wording is the implementation's own, but a rejected subject names
378
+ the path of the first problem (`subject.price`, `subject.slots[0]`).
379
+
380
+ ## 13. Open questions
381
+
382
+ 1. Whether v1 needs a free-text `note` field. Starting without one.
383
+ 2. Where sealing lands in the envelope when end-to-end encryption arrives.
384
+ 3. Handles and discovery — deferred until there is a network to search.
385
+ 4. More than two parties in a room.
386
+ 5. A protocol-level way for the human, not only the agent, to set limits.