docs: requests that outlive a turn, and SDK 1.28.0 (#1635) - #1860
Conversation
jhgaylor
left a comment
There was a problem hiding this comment.
Requesting changes. One blocker, and it's the kind that passes every gate and then quietly does nothing.
The SDK version collides with one already released.
This PR bumps the TypeScript SDK to 1.26.0. main is already at 1.26.0 — the acp runtime stack took that number, the tag sdk-v1.26.0 exists, and main's changelog has its own ## [1.26.0] — 2026-09-10 entry:
main: ## [1.26.0] — 2026-09-10 ### Changed (runtime_command, acp runtime, Agent["model"] nullable)
this PR: ## [1.26.0] — 2026-09-10 ### Added (pending_requests, turn waiting, ask_timeout)
The gate is green here only because it compares against this PR's base, which is still 1.25.0. After the rebase onto main the version is unchanged relative to main, so nothing publishes — the detached-request types land on main unreleased, which is precisely the state the gate exists to prevent. Nobody finds out until someone installs the SDK and the types aren't there. git merge-tree already flags sdk/typescript/CHANGELOG.md as a conflict against main, which is the visible half of the same problem.
Please move this to 1.27.0 across all four edits:
sdk/typescript/package.jsonsdk/typescript/package-lock.jsonUSER_AGENTinsdk/typescript/src/http.ts(currentlyfountain-sdk-js/1.26.0, identical tomain's)- the
sdk/typescript/CHANGELOG.mdheading
Worth noting npm's latest published is still 1.24.0 while main carries 1.26.0 and the tag exists, so it's worth checking that the 1.25.0/1.26.0 publishes actually completed before adding another on top. A failed publish is invisible to every gate we have.
(The Elixir static analysis red is #1858's deleted blank line.)
The prose is genuinely good and I'd merge it as-is otherwise. The ADR 0014 amendment does the thing the ADR rules ask for and that amendments usually skip: it says what the original paragraph was right about (a request held inside a running turn, the idle bound, the cost rule) before saying what it doesn't reach. Anyone reading 0014 top to bottom now ends up with an accurate model instead of two paragraphs that appear to contradict each other. The "Nothing is resolved that cannot be delivered" paragraph in particular is the invariant the whole stack is built around, and having it stated once in the ADR rather than only in five code comments is what makes it survivable.
Small thing, take it or leave it: 0014's frontmatter still carries verified: 2026-08-24 and the index row is unchanged. The description didn't change so scripts/decisions-index.sh regenerates identically, but the ADR now describes behaviour that verification date predates.
63e6c12 to
6c17770
Compare
482ea01 to
e7c4729
Compare
6c17770 to
78eefd7
Compare
e7c4729 to
990b0d9
Compare
This is the same trap that already bit each stack once: 1.26.0 was taken by #1634 mid-flight, then 1.27.0 was taken by #1849/#1637 mid-flight. The gate compares against the PR's base, not Before merging whichever of these goes first: leave its number alone, then re-number the other to Two further re-cut stacks (#1565 reapply, #1125 OAuth self-service) are in flight and will each want a number too. Worth deciding the merge order for all four in one go rather than discovering it a gate at a time. |
|
All three of these claim
Each is individually correct and all three release gates are green, because the gate compares against the PR's base, not Merging any one of these obliges re-numbering the other two before they merge — A fourth stack (#1565, conversation reapply) is still being cut and will want a number too. Given that, it is probably worth picking the merge order for all four now and assigning |
|
All four are individually correct and all four release gates are green, because the gate compares against each PR's base, not Recommendation: take the version bump out of all four, and do one SDK release afterwards. Assigning Instead:
That removes the ordering constraint entirely, gives users one coherent release note instead of four near-simultaneous minors, and means no stack can silently publish nothing by merging second. The cost is that the SDK types for whichever features land first sit unreleased on |
BinaryBourbon
left a comment
There was a problem hiding this comment.
Approving. The right documents changed and nothing describes behaviour that is not in the stack.
The 0014 amendment is the piece that had to be got right, and it is: it does not quietly rewrite the paragraph about the five-minute ceiling, it says that paragraph is still correct for a request held inside a running turn and that a second shape now exists beside it. It also keeps the cost rule this ADR exists to defend explicit — "No sandbox is held open, so the cost rule this ADR keeps is untouched: what changes is that the card outlives the turn, not the machine" — and records the one genuine departure (the connection not surviving the detach, against #817's sandbox-scoped rule) with the per-turn request-id collision as the reason. That is the sentence someone will need in six months. okf validate is green.
docs/concepts/permissions.md folds the new timeout into the existing "Nobody is also an answer" rule rather than appending a section that contradicts it, which is what the acceptance criterion asked for, and the deadline precedence is given as the three-item list with the shorter-wins rule and why (the request is written inside the sandbox, the policy belongs to the tenant). The note that a client can tell a resume turn from a typed prompt by the prompt being one JSON object under fountain/permission_answer is a useful thing to have written down, since origin stays user.
The SDK bump is a correct four-part edit — package.json, package-lock.json, USER_AGENT in src/http.ts, and the ## [1.28.0] heading — and it is in the last PR of the stack, which is the rule that keeps concurrent bumps from racing on main.
One small thing, non-blocking: the CHANGELOG entry and the docs both explain that answering opens a new turn, but neither says that the resume turn is a turn like any other for billing — it burns turn time, and an expiry sweep does too. Worth a clause if it is cheap; a deterministic operation whose approvals arrive as separate turns is the shape this feature invites, and the person budgeting it will ask.
No findings.
78eefd7 to
82ea60a
Compare
990b0d9 to
4264c3e
Compare
|
Pushed
The CHANGELOG line gained "and at most a year either way" in the same place it already described the two timeouts. Prose gates, measured before and after on this page: vale STE goes 115 → 117 problems, and the delta is two Nothing else in the PR moved. The 0014 amendment, the SDK 1.28.0 bump and the rest of the page are as you reviewed them. |
BinaryBourbon
left a comment
There was a problem hiding this comment.
Re-approving for aae4bf83. The ceiling needed a sentence on the page that explains the deadline, and this is the right one: it says what the limit is, where it is enforced, and what it is not, and it records that a per-request _meta.fountain.timeout above the same limit falls back rather than being refused — which is the asymmetry a reader would otherwise have to discover. The CHANGELOG clause ("and at most a year either way") lands in the right paragraph.
The new paragraph is clean under all three prose gates. I ran them rather than taking the commit message's word for it.
Non-blocking: the page picked up three STE sentence-length errors, and they are from the earlier commit
vale --config=.vale-ste.yml docs/concepts/permissions.md reports 5 STE.SentenceLength errors on this branch against 2 on origin/main. None of the three new ones are in aae4bf83; they came in with 4264c3e4, and I missed them on the first read.
docs/concepts/permissions.md:130 Sentence has 34 words; the description limit is 25.
docs/concepts/permissions.md:136 Sentence has 36 words; the description limit is 25.
docs/concepts/permissions.md:158 Sentence has 27 words; the description limit is 25.
- 130 — "A client that wants to render this as a system event and not as something a person typed can tell it by the prompt itself, which is one JSON object whose only key is
fountain/permission_answer." - 136 — "The old peer is still holding that request, and the runtimes number their requests from 0 on each turn, so keeping it would make the resume turn's first request collide with the one it still holds."
- 158 — "The request is written inside the sandbox and the policy belongs to the tenant, so an agent can bound its own wait and cannot extend the tenant's."
prose-advice.sh exits 0 unconditionally, so none of this blocks and I am not holding the stack for it. But .valeignore is empty and the rule is that every published page is written in STE, so this is three lines of advisory report that will now sit on the page indefinitely. Each is a comma splice away from being two sentences and losing nothing. Worth a follow-up commit here while the page is open, rather than an issue.
(Separately: python3 scripts/docs-style.py reports two findings on docs/catalog/runtimes/codex.md:62 and docs/concepts/environment.md:73. Both are already on main and neither file is touched by this stack. Not yours.)
Standing from the previous read
Still no clause saying the resume turn is a turn like any other for billing — it burns turn time, and so does the expiry sweep's. A deterministic operation whose approvals arrive as separate turns is exactly the shape this feature invites, and the person budgeting it will ask. Cheap to add beside the timeout paragraph; still not blocking.
The 0014 amendment, the deadline precedence list, the origin: user note and the four-part SDK bump all read the same as they did. okf validate green.
82ea60a to
1319fee
Compare
aae4bf8 to
7b1b6c5
Compare
The permissions page gets the waiting stop reason, the deadline rule and the answer that opens a resume turn; ADR 0014 records the second shape of request and why the answer travels as one line of JSON in the prompt rather than a _meta field the peer has no hook for. The TypeScript SDK ships the generated types as 1.28.0. The number has moved twice while this stack was open: 1.26.0 went to #1634 and 1.27.0 to #1637, and both are tagged. Whichever release lands next re-bumps again. The bump is four edits, not three: package.json, the lockfile's two entries, the USER_AGENT in http.ts and the changelog heading, which sits above the 1.27.0 entry rather than replacing it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S9jevFQT5MkF3rJUieeiHW
The bound added in this stack's base commit is refused at two doors and named in the generated API reference, but the manual page that explains the deadline did not mention it. A reader who set a longer one would meet the refusal with nothing written down to explain it. Says what the limit is, where it is enforced, and what it is not: a database timestamp cannot hold a longer deadline, so this is not a statement about how long a wait is useful. Also records that a per-request `_meta.fountain.timeout` above the limit falls back rather than being refused, because that half has no door to be refused at. vale, destink and docs-style add no new findings on the page. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011zS1mqiBmjRa4q956LTKDG
7b1b6c5 to
43c7690
Compare
The permissions page, ADR 0014, the CHANGELOG and SDK 1.28.0.
The version has moved twice while this stack was open: 1.26.0 went to #1634 (#1839) and 1.27.0 to #1637 (#1849), and both are tagged. So this bump is 1.28.0, and whichever SDK release lands next re-bumps again. The bump is four edits, not three:
package.json, the lockfile's two entries, theUSER_AGENTinsrc/http.tsand the changelog heading, which sits above the 1.27.0 entry rather than replacing it.Part 9 of 9 for #1635, on #1859.
🤖 Generated with Claude Code
https://claude.ai/code/session_01S9jevFQT5MkF3rJUieeiHW
Closes #1635