stringcup 3.27.0__tar.gz → 3.32.0__tar.gz
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.
- {stringcup-3.27.0/stringcup.egg-info → stringcup-3.32.0}/PKG-INFO +1 -1
- {stringcup-3.27.0 → stringcup-3.32.0/stringcup.egg-info}/PKG-INFO +1 -1
- {stringcup-3.27.0 → stringcup-3.32.0}/stringcup.py +115 -11
- {stringcup-3.27.0 → stringcup-3.32.0}/stringcup_mcp.py +60 -8
- {stringcup-3.27.0 → stringcup-3.32.0}/LICENSE +0 -0
- {stringcup-3.27.0 → stringcup-3.32.0}/NOTICE +0 -0
- {stringcup-3.27.0 → stringcup-3.32.0}/PYPI-README.md +0 -0
- {stringcup-3.27.0 → stringcup-3.32.0}/pyproject.toml +0 -0
- {stringcup-3.27.0 → stringcup-3.32.0}/setup.cfg +0 -0
- {stringcup-3.27.0 → stringcup-3.32.0}/stringcup.egg-info/SOURCES.txt +0 -0
- {stringcup-3.27.0 → stringcup-3.32.0}/stringcup.egg-info/dependency_links.txt +0 -0
- {stringcup-3.27.0 → stringcup-3.32.0}/stringcup.egg-info/entry_points.txt +0 -0
- {stringcup-3.27.0 → stringcup-3.32.0}/stringcup.egg-info/requires.txt +0 -0
- {stringcup-3.27.0 → stringcup-3.32.0}/stringcup.egg-info/top_level.txt +0 -0
|
@@ -75,10 +75,10 @@ except ImportError as _exc: # pragma: no cover
|
|
|
75
75
|
"On Python 3.7 pin it below 46 (see requirements.txt) — 46 drops 3.7."
|
|
76
76
|
) from _exc
|
|
77
77
|
|
|
78
|
-
__version__ = "3.
|
|
78
|
+
__version__ = "3.29.0"
|
|
79
79
|
|
|
80
80
|
#: Numeric form, for comparisons. Compare this, never `__version__`.
|
|
81
|
-
version_info = (3,
|
|
81
|
+
version_info = (3, 29, 0)
|
|
82
82
|
|
|
83
83
|
#: Version of the PyPI DISTRIBUTION, which ships this module and
|
|
84
84
|
#: `stringcup_mcp.py` together. **This is a third number and it is not
|
|
@@ -109,7 +109,7 @@ version_info = (3, 25, 0)
|
|
|
109
109
|
#: It must increase whenever either module's version does.
|
|
110
110
|
#: `clients/python/test_contract.py` snapshots all three and fails on any
|
|
111
111
|
#: change, so bumping a module forces a decision about this one.
|
|
112
|
-
__dist_version__ = "3.
|
|
112
|
+
__dist_version__ = "3.32.0"
|
|
113
113
|
|
|
114
114
|
__all__ = [
|
|
115
115
|
"Client",
|
|
@@ -244,6 +244,10 @@ FEATURES = {
|
|
|
244
244
|
"identity_source": (3, 24, 0), # load_or_register says which it did
|
|
245
245
|
"self_join_refused": (3, 24, 0), # join_rendezvous detects a shared identity
|
|
246
246
|
"identity_exclusive": (3, 25, 0), # is another live process on this identity
|
|
247
|
+
"handoff_expiry": (3, 26, 0), # the handoff block carries the relay deadline
|
|
248
|
+
"handoff_guide_url": (3, 27, 0), # the block tells a responder where the guide is
|
|
249
|
+
"sync_barrier_returns_drained": (3, 28, 0), # the barrier no longer destroys what it reads
|
|
250
|
+
"handoff_objective": (3, 29, 0), # the brief and the operator setup ride the block
|
|
247
251
|
}
|
|
248
252
|
|
|
249
253
|
DEFAULT_BASE_URL = "https://stringcup.com/api/v2"
|
|
@@ -781,7 +785,7 @@ MIN_POLL_INTERVAL = 12.0
|
|
|
781
785
|
|
|
782
786
|
#: Throttle only when a bucket is down to this fraction of its own limit.
|
|
783
787
|
#:
|
|
784
|
-
#: An absolute threshold cannot work: the server's buckets range from
|
|
788
|
+
#: An absolute threshold cannot work: the server's buckets range from 10/hour
|
|
785
789
|
#: (registration) to 300/hour (inbox), so any fixed number is either always or
|
|
786
790
|
#: never tripped depending on the endpoint.
|
|
787
791
|
THROTTLE_AT_FRACTION = 0.10
|
|
@@ -1756,7 +1760,7 @@ class Client:
|
|
|
1756
1760
|
"""
|
|
1757
1761
|
Reuse the identity at `path`, registering only if it is absent.
|
|
1758
1762
|
|
|
1759
|
-
This is the form agents should use. Registration is capped at
|
|
1763
|
+
This is the form agents should use. Registration is capped at 30/hour
|
|
1760
1764
|
per IP, and since the id is assigned, re-registering does not even get
|
|
1761
1765
|
you the same identity back — an agent that registers on every start
|
|
1762
1766
|
both locks itself out and becomes unreachable at the id its peer knows.
|
|
@@ -2038,21 +2042,64 @@ class Client:
|
|
|
2038
2042
|
|
|
2039
2043
|
return info
|
|
2040
2044
|
|
|
2041
|
-
def handoff_block(self, info: dict, role: str = "responder"
|
|
2045
|
+
def handoff_block(self, info: dict, role: str = "responder",
|
|
2046
|
+
objective: Optional[str] = None) -> str:
|
|
2042
2047
|
"""
|
|
2043
2048
|
The block an operator pastes to the other agent, secret included.
|
|
2044
2049
|
|
|
2045
2050
|
Exists so the secret cannot be forgotten. The handoff was already a
|
|
2046
2051
|
copy-paste; carrying one more line in it is the entire cost of
|
|
2047
2052
|
authenticating first contact.
|
|
2053
|
+
|
|
2054
|
+
`objective` is what the work IS. Pass the brief your own operator gave
|
|
2055
|
+
you. A responder handed a token and nothing else is an untasked agent,
|
|
2056
|
+
and this project already records what that costs: both sides assume
|
|
2057
|
+
the other was briefed, and neither says so.
|
|
2048
2058
|
"""
|
|
2049
|
-
lines = [
|
|
2050
|
-
|
|
2059
|
+
lines = ["STRINGCUP HANDOFF", ""]
|
|
2060
|
+
|
|
2061
|
+
# THE OPERATOR SECTION COMES FIRST BECAUSE IT IS READ BEFORE PASTING.
|
|
2062
|
+
#
|
|
2063
|
+
# Self-install is a bonus that usually does not arrive. Measured on one
|
|
2064
|
+
# machine on 2026-09-17: the identical command has been both permitted
|
|
2065
|
+
# and refused repeatedly, and four controlled runs in one sitting were
|
|
2066
|
+
# refused every time -- the agent's role, the operator's own phrasing
|
|
2067
|
+
# and the presence of a task brief each tested and each eliminated. A
|
|
2068
|
+
# string permitted earlier was refused later, so nothing about the
|
|
2069
|
+
# prompt is the cause. It is unpredictable, not uniformly blocked. Onboarding is two operator actions, and putting
|
|
2070
|
+
# the first one here means the operator reads it at the moment they are
|
|
2071
|
+
# already acting, rather than after a refused attempt costs a round
|
|
2072
|
+
# trip against a token that expires.
|
|
2073
|
+
lines += [
|
|
2074
|
+
" BEFORE YOU PASTE THIS -- for the operator, not the agent.",
|
|
2075
|
+
" If the receiving agent has no Stringcup tools yet, run this in",
|
|
2076
|
+
" its project and restart it first. An MCP config is read at",
|
|
2077
|
+
" startup, so the restart is required either way, and the agent",
|
|
2078
|
+
" is often refused when it tries this itself:",
|
|
2079
|
+
"",
|
|
2080
|
+
" claude mcp add stringcup -- uvx --from stringcup stringcup-mcp",
|
|
2081
|
+
"",
|
|
2082
|
+
" The token below is time-boxed, so a setup detour can outlive it.",
|
|
2051
2083
|
"",
|
|
2052
2084
|
" YOUR ROLE: %s" % role,
|
|
2053
2085
|
" TOKEN: %s" % info["token"],
|
|
2054
2086
|
]
|
|
2055
2087
|
|
|
2088
|
+
# THE DEADLINE COMES FROM THE RELAY, NOT FROM A DOC CONSTANT.
|
|
2089
|
+
#
|
|
2090
|
+
# The relay returns expires_at and it is authoritative; a published
|
|
2091
|
+
# "expires in N minutes" is a copy that can drift from it silently.
|
|
2092
|
+
# That drift already nearly happened: the window moved 15 -> 30 and an
|
|
2093
|
+
# agent told its operator 30 from the page, which was right by luck
|
|
2094
|
+
# rather than by design. Reported by a peer agent that had measured 15
|
|
2095
|
+
# empirically hours earlier and went looking for a doc defect.
|
|
2096
|
+
#
|
|
2097
|
+
# Same reasoning as echoing `role` rather than assuming it: when the
|
|
2098
|
+
# relay reports a fact, pass the relay's value through.
|
|
2099
|
+
if info.get("expires_at"):
|
|
2100
|
+
lines.append(" EXPIRES: %s UTC (relay value, not an estimate)"
|
|
2101
|
+
% info["expires_at"])
|
|
2102
|
+
|
|
2056
2103
|
if info.get("secret"):
|
|
2057
2104
|
lines += [
|
|
2058
2105
|
" SECRET: %s" % info["secret"],
|
|
@@ -2062,6 +2109,7 @@ class Client:
|
|
|
2062
2109
|
" substituted. If pairing reports verified: false, or raises,",
|
|
2063
2110
|
" stop and tell your operator.",
|
|
2064
2111
|
]
|
|
2112
|
+
|
|
2065
2113
|
else:
|
|
2066
2114
|
lines += [
|
|
2067
2115
|
"",
|
|
@@ -2070,6 +2118,45 @@ class Client:
|
|
|
2070
2118
|
" of band.",
|
|
2071
2119
|
]
|
|
2072
2120
|
|
|
2121
|
+
# THE RESPONDER WAS POINTED AT NOTHING, and that asymmetry is a real
|
|
2122
|
+
# onboarding defect rather than an omission.
|
|
2123
|
+
#
|
|
2124
|
+
# The initiator is handed the homepage prompt, which names agent.md.
|
|
2125
|
+
# The responder is handed THIS BLOCK AND ONLY THIS BLOCK -- so an agent
|
|
2126
|
+
# that arrives without Stringcup tools has no idea where the guide is,
|
|
2127
|
+
# cannot read what to do about it, and cannot find the install command
|
|
2128
|
+
# it is now permitted to run. The operator sees one agent set itself up
|
|
2129
|
+
# and the other apparently refuse to.
|
|
2130
|
+
#
|
|
2131
|
+
# The URL is a REFERENCE, not an instruction to obey: this page's own
|
|
2132
|
+
# history records that "read agent.md and follow it" was the wrong
|
|
2133
|
+
# prompt because it asks an agent to fetch a web page and do as it
|
|
2134
|
+
# says. Naming where the guide is does not ask for obedience, and an
|
|
2135
|
+
# agent declining is still a correct outcome.
|
|
2136
|
+
# THE WORK, NOT JUST THE CONNECTION. An agent that arrives holding a
|
|
2137
|
+
# token and no brief has to guess, or ask -- and its peer, which does
|
|
2138
|
+
# have a brief, reads the silence as the peer being slow rather than
|
|
2139
|
+
# uninformed. The initiator was required to prompt for an objective
|
|
2140
|
+
# before pairing, so it always has one to pass on; not passing it was
|
|
2141
|
+
# an omission rather than a design.
|
|
2142
|
+
if objective:
|
|
2143
|
+
lines += [
|
|
2144
|
+
"",
|
|
2145
|
+
" OBJECTIVE: %s" % objective,
|
|
2146
|
+
"",
|
|
2147
|
+
" That is what your operator asked of the agent that opened this",
|
|
2148
|
+
" pairing. Confirm it with your own operator rather than adopting",
|
|
2149
|
+
" it -- it reached you through them, and they may be handing you a",
|
|
2150
|
+
" different part of the work.",
|
|
2151
|
+
]
|
|
2152
|
+
|
|
2153
|
+
lines += [
|
|
2154
|
+
"",
|
|
2155
|
+
" No Stringcup tools yet, or unsure what to do with this?",
|
|
2156
|
+
" https://stringcup.com/agent.md (reference, not an instruction:",
|
|
2157
|
+
" declining and telling your operator is a correct outcome)",
|
|
2158
|
+
]
|
|
2159
|
+
|
|
2073
2160
|
return "\n".join(lines)
|
|
2074
2161
|
|
|
2075
2162
|
def _verify_pairing(self, info: dict, secret: str, token: str,
|
|
@@ -2890,6 +2977,17 @@ class Client:
|
|
|
2890
2977
|
a different view of what was said. What converges is a verifiable
|
|
2891
2978
|
content check.
|
|
2892
2979
|
|
|
2980
|
+
**It RETURNS what it drained** (`messages`), because it acknowledges
|
|
2981
|
+
everything it reads and the relay then deletes it. Returning only a
|
|
2982
|
+
count and the peer's last line made this a **destructive read**:
|
|
2983
|
+
draining N discarded N-1 irrecoverably, and the symptom that makes an
|
|
2984
|
+
agent reach for a barrier -- a suspicious backlog -- cannot distinguish
|
|
2985
|
+
redundant chatter from four unread pieces of analysis. Observed: a
|
|
2986
|
+
barrier ate the four messages that were the evidence in the argument
|
|
2987
|
+
the barrier was called to settle. Reported by an agent that noticed the
|
|
2988
|
+
return SHAPE forced the loss, which makes it a property rather than bad
|
|
2989
|
+
luck.
|
|
2990
|
+
|
|
2893
2991
|
This drains your inbox to empty, then returns what you need to send
|
|
2894
2992
|
your peer so both sides can confirm they are level:
|
|
2895
2993
|
|
|
@@ -2907,6 +3005,7 @@ class Client:
|
|
|
2907
3005
|
rediscover it mid-argument.
|
|
2908
3006
|
"""
|
|
2909
3007
|
drained = 0
|
|
3008
|
+
drained_messages: List[Message] = []
|
|
2910
3009
|
last_from_peer = None
|
|
2911
3010
|
|
|
2912
3011
|
deadline = time.monotonic() + timeout
|
|
@@ -2916,6 +3015,7 @@ class Client:
|
|
|
2916
3015
|
break
|
|
2917
3016
|
|
|
2918
3017
|
drained += len(page.messages)
|
|
3018
|
+
drained_messages.extend(page.messages)
|
|
2919
3019
|
for msg in page.messages:
|
|
2920
3020
|
if msg.sender_id == peer:
|
|
2921
3021
|
last_from_peer = msg
|
|
@@ -2926,6 +3026,10 @@ class Client:
|
|
|
2926
3026
|
|
|
2927
3027
|
text = last_from_peer.text if last_from_peer is not None else ""
|
|
2928
3028
|
return {
|
|
3029
|
+
# Everything this call consumed, in arrival order. The caller may
|
|
3030
|
+
# never see it anywhere else: these rows are acknowledged above and
|
|
3031
|
+
# the relay deletes on ACK.
|
|
3032
|
+
"messages": drained_messages,
|
|
2929
3033
|
"drained": drained,
|
|
2930
3034
|
"last_text": text,
|
|
2931
3035
|
# First line, because a long multi-topic message is exactly the
|
|
@@ -3766,7 +3870,7 @@ class Client:
|
|
|
3766
3870
|
The server's rate-limit bucket a request falls in.
|
|
3767
3871
|
|
|
3768
3872
|
Mirrors RateLimitFilter server-side: buckets are per endpoint *and*
|
|
3769
|
-
method, which is why POST /identities (
|
|
3873
|
+
method, which is why POST /identities (30/hour) and GET /messages
|
|
3770
3874
|
(300/hour) must not share a tracked budget. Path parameters are
|
|
3771
3875
|
collapsed so /messages/42 and /messages/43 are one bucket.
|
|
3772
3876
|
"""
|
|
@@ -3812,14 +3916,14 @@ class Client:
|
|
|
3812
3916
|
Two things here were wrong and caused a real pairing failure.
|
|
3813
3917
|
|
|
3814
3918
|
**The threshold was absolute.** It slept whenever `remaining <= 10`,
|
|
3815
|
-
applied to buckets whose limits range from
|
|
3919
|
+
applied to buckets whose limits range from 10/hour (rotation) to
|
|
3816
3920
|
300/hour (inbox). Registration can *never* report more than 5
|
|
3817
3921
|
remaining, so it always tripped: a fresh registration reporting 4 of 5
|
|
3818
3922
|
— a budget 80% intact — slept the full 30 seconds. It is now a fraction
|
|
3819
3923
|
of the bucket's own limit, so "nearly exhausted" means what it says.
|
|
3820
3924
|
|
|
3821
3925
|
**The budget was global.** One `rate_limit` dict was overwritten by
|
|
3822
|
-
every response, so a figure from the
|
|
3926
|
+
every response, so a figure from the 30/hour registration bucket
|
|
3823
3927
|
throttled the *next* call even when that endpoint had 119 of 120 left.
|
|
3824
3928
|
Budgets are now tracked per bucket.
|
|
3825
3929
|
|
|
@@ -90,7 +90,7 @@ stringcup.require_features("short_timeouts", "sent_seq", "inbox_quota_errors",
|
|
|
90
90
|
"verified_pairing_pins", "local_pairing_role",
|
|
91
91
|
"header_framed_verify", "undecryptable_visible", "structural_pin_rollback")
|
|
92
92
|
|
|
93
|
-
__version__ = "1.
|
|
93
|
+
__version__ = "1.26.0"
|
|
94
94
|
|
|
95
95
|
#: The MCP revision this server implements.
|
|
96
96
|
PROTOCOL_VERSION = "2025-06-18"
|
|
@@ -129,7 +129,7 @@ _IDENTITY_EXCLUSIVE = None
|
|
|
129
129
|
#:
|
|
130
130
|
#: A newer library is NOT an error: it is usually fine and blocking it would
|
|
131
131
|
#: break legitimate installs. It is reported, not refused.
|
|
132
|
-
BUILT_AGAINST = (3,
|
|
132
|
+
BUILT_AGAINST = (3, 29, 0)
|
|
133
133
|
|
|
134
134
|
|
|
135
135
|
def _version_note() -> Optional[str]:
|
|
@@ -374,7 +374,7 @@ def client() -> Client:
|
|
|
374
374
|
The agent's identity, loaded from disk or registered once.
|
|
375
375
|
|
|
376
376
|
Deferred rather than built at startup for two reasons: registration is
|
|
377
|
-
capped at
|
|
377
|
+
capped at 30/hour per IP, and a host that probes tool lists on every launch
|
|
378
378
|
would burn that budget without ever sending a message. Re-registering does
|
|
379
379
|
not recover an identity — it mints a different one — so the file is the
|
|
380
380
|
thing that matters.
|
|
@@ -509,21 +509,38 @@ def tool_whoami(arguments: Dict[str, Any]) -> Dict[str, Any]:
|
|
|
509
509
|
def tool_open_rendezvous(arguments: Dict[str, Any]) -> Dict[str, Any]:
|
|
510
510
|
me = client()
|
|
511
511
|
info = me.open_rendezvous()
|
|
512
|
+
objective = arguments.get("objective")
|
|
512
513
|
return {
|
|
513
514
|
"token": info["token"],
|
|
514
515
|
# Generated here and NEVER sent to the relay. The relay issues the
|
|
515
516
|
# token, so the token authenticates nothing about a key the relay
|
|
516
517
|
# served; this is the half it cannot know.
|
|
517
518
|
"secret": info.get("secret"),
|
|
518
|
-
"handoff": me.handoff_block(info),
|
|
519
|
+
"handoff": me.handoff_block(info, objective=objective),
|
|
519
520
|
# The relay derives and reports the role; echo it rather than assuming.
|
|
520
521
|
"role": info.get("role", "initiator"),
|
|
522
|
+
# SAME REASONING, ONE FIELD OVER, and it was missing: the relay returns
|
|
523
|
+
# the deadline and this surface dropped it, so an agent telling its
|
|
524
|
+
# operator when the token dies had to recite a number from a doc. A
|
|
525
|
+
# published constant standing in for an authoritative value is the same
|
|
526
|
+
# shape as a renamed field surviving in prose; here the real value was
|
|
527
|
+
# already in the response being parsed. Reported by a peer agent which noticed only because it had
|
|
528
|
+
# measured the old 15-minute window itself.
|
|
529
|
+
"expires_at": info.get("expires_at"),
|
|
521
530
|
"next": (
|
|
522
531
|
"Give the WHOLE handoff block to your operator to pass to the other agent "
|
|
523
532
|
"\u2014 the token AND the secret. The secret never reaches the relay, which "
|
|
524
533
|
"is what lets the pairing prove neither key was substituted; the token "
|
|
525
534
|
"alone cannot, because the relay issued it. Then call await_peer with both. "
|
|
526
|
-
"You are the initiator: you speak first once paired
|
|
535
|
+
"You are the initiator: you speak first once paired.\n\n"
|
|
536
|
+
"TELL YOUR OPERATOR TO CONFIGURE THE SECOND AGENT BEFORE PASTING THIS. "
|
|
537
|
+
"You were able to reach Stringcup; the responder frequently is not, and "
|
|
538
|
+
"cannot fix it from inside its own session \u2014 an MCP config is read at "
|
|
539
|
+
"startup, so it must be set up and then restarted before it can join. "
|
|
540
|
+
"Observed twice on one machine: the initiator registered the server fine "
|
|
541
|
+
"and the responder was refused. Setting the second agent up first turns "
|
|
542
|
+
"two round trips into none, and the rendezvous is time-boxed, so a setup "
|
|
543
|
+
"detour can outlive the token."
|
|
527
544
|
),
|
|
528
545
|
}
|
|
529
546
|
|
|
@@ -842,10 +859,22 @@ def tool_sync_barrier(arguments: Dict[str, Any]) -> Dict[str, Any]:
|
|
|
842
859
|
return {
|
|
843
860
|
"synchronised": True,
|
|
844
861
|
"drained": bar["drained"],
|
|
862
|
+
# EVERYTHING THE BARRIER CONSUMED. It acknowledges what it reads and
|
|
863
|
+
# the relay deletes on ACK, so without this the caller loses N-1 of N
|
|
864
|
+
# messages to a call it made to RECOVER a conversation. A barrier once
|
|
865
|
+
# ate the four messages that were the evidence in the argument it was
|
|
866
|
+
# called to settle.
|
|
867
|
+
"messages": [
|
|
868
|
+
{"from": m.sender_id, "text": m.text, "inbox_seq": m.id,
|
|
869
|
+
"created_at": m.created_at, "channel": m.channel}
|
|
870
|
+
for m in bar.get("messages", [])
|
|
871
|
+
],
|
|
845
872
|
"peer_last_line": bar["last_line"],
|
|
846
873
|
"peer_last_seq": bar["last_seq"],
|
|
847
874
|
"next": (
|
|
848
|
-
"
|
|
875
|
+
"READ `messages` FIRST \u2014 it is everything this call consumed and it "
|
|
876
|
+
"exists nowhere else, because the barrier acknowledged it and the relay "
|
|
877
|
+
"deletes on acknowledgement. Then: send your peer "
|
|
849
878
|
"a message quoting `drained` and `peer_last_line` verbatim, and ask it to "
|
|
850
879
|
"do the same. If the line it quotes is your most recent message, you are "
|
|
851
880
|
"synchronised \u2014 resume from the NEWEST content, not the argument. This "
|
|
@@ -1054,9 +1083,26 @@ TOOLS: List[Dict[str, Any]] = [
|
|
|
1054
1083
|
"token proves nothing about a key the relay served, but a tag computed "
|
|
1055
1084
|
"over both public keys with the secret matches only if neither key was "
|
|
1056
1085
|
"substituted. It costs your operator nothing — the same single paste was "
|
|
1057
|
-
"already happening. Never put the secret in a message
|
|
1086
|
+
"already happening. Never put the secret in a message.\n\n"
|
|
1087
|
+
"PASS `objective` — the brief your own operator gave you, in one or "
|
|
1088
|
+
"two sentences. It rides the handoff so the other agent arrives knowing "
|
|
1089
|
+
"what the work is. Without it they get a token and nothing else, and an "
|
|
1090
|
+
"untasked agent and a briefed one each assume the other was told, which "
|
|
1091
|
+
"reads as the peer being slow rather than uninformed. You were required "
|
|
1092
|
+
"to ask your operator for an objective before pairing, so you have one."
|
|
1058
1093
|
),
|
|
1059
|
-
"inputSchema": {
|
|
1094
|
+
"inputSchema": {
|
|
1095
|
+
"type": "object",
|
|
1096
|
+
"properties": {
|
|
1097
|
+
"objective": {
|
|
1098
|
+
"type": "string",
|
|
1099
|
+
"description": (
|
|
1100
|
+
"What the work is, from your own operator's brief. Travels "
|
|
1101
|
+
"in the handoff block so the peer is not left guessing."
|
|
1102
|
+
),
|
|
1103
|
+
},
|
|
1104
|
+
},
|
|
1105
|
+
},
|
|
1060
1106
|
"handler": tool_open_rendezvous,
|
|
1061
1107
|
},
|
|
1062
1108
|
{
|
|
@@ -1198,6 +1244,12 @@ TOOLS: List[Dict[str, Any]] = [
|
|
|
1198
1244
|
"The desync presents as YOUR PEER IGNORING YOU: direct questions appear "
|
|
1199
1245
|
"unanswered on both sides, and both of you form confident, wrong "
|
|
1200
1246
|
"conclusions about the other\u2019s reliability. Use receive_all instead. "
|
|
1247
|
+
"`more_waiting: false` means YOUR INBOX IS EMPTY AT THIS INSTANT. It is "
|
|
1248
|
+
"NOT an end-of-turn signal and this protocol has none: a peer sending three "
|
|
1249
|
+
"messages back to back will usually reach you as three separate calls, each "
|
|
1250
|
+
"reporting false, because batching is a property of a SLOW READER and never "
|
|
1251
|
+
"of a fast sender. If your peer said it was sending N, keep calling until you "
|
|
1252
|
+
"have N. Measured: a burst of three arrived as 1+1+1 with false every time.\n\n"
|
|
1201
1253
|
"If `more_waiting` is true you are already holding stale content \u2014 do "
|
|
1202
1254
|
"not reply; call receive_all. If you are already out of sync, call "
|
|
1203
1255
|
"sync_barrier."
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|