Repository navigation
feat(agents): an assistant's grant may last a year, and agent.list says so - #1686
Conversation
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
…ys so AgentMaxMs goes from 30 days to 365. support (14 days) and release (90 days) stay. The box refuses anything longer with E_CAP_EXCEEDED, as before. agent.list now carries longestMs, the longest grant the box takes for each role, so a newer app offers an older box no more than the 30 days it takes. A box without longestMs is taken to allow 30 days for an agent. agent.expiring still fires once, in a grant's last day, whatever its length. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The app checks the same bytes; the length is only data in them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: aac7a0f6f5
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| @@ -0,0 +1,5 @@ | |||
| --- | |||
| "ftw": patch | |||
There was a problem hiding this comment.
Mark the new access duration as a minor change
Use a minor changeset here: extending assistant access from 30 days to one year is a visible new capability, not a fix. Leaving this as patch causes the release tooling to publish the feature under the wrong version increment.
AGENTS.md reference: AGENTS.md:L171-L175
Useful? React with 👍 / 👎.
| defer h.cfg.Agents.tell("", protocol.AgentDecided{}) | ||
| now, _ := h.wall() | ||
| out := protocol.AgentList{Agents: []protocol.AgentRow{}, Keys: []protocol.ApprovalKeyInfo{}} | ||
| out := protocol.AgentList{Agents: []protocol.AgentRow{}, Keys: []protocol.ApprovalKeyInfo{}, LongestMs: protocol.LongestMs()} |
There was a problem hiding this comment.
Expose grant limits through the local agents endpoint
Mirror LongestMs into handleAgentsList in go/internal/api/api_agents.go. The session-based agent.list is populated here, but the box page reads GET /api/agents, whose protocol.AgentList constructor still omits this field and therefore emits longestMs: null. A box-page client following the documented missing-limit fallback consequently treats this new box like an older one and caps assistant access at 30 days instead of offering the new one-year duration.
Useful? React with 👍 / 👎.
The Access tab now offers 1, 7, 30 and 90 days and a year, never past the offer's ttlS or the box's longest grant for the role, which GET /api/agents now carries in box.longestMs. It still starts at a week. Renewing follows the same rule, so a support row is no longer offered 30 days that the box would refuse. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
aac7a0f to
70c7719
Compare
go.mod: main pins go/protocol at cbdd8a58 (#1687, fleetwire and webpush); this branch needs AgentMaxMs from srcfl/ftw#1686. Pin both at #1686's branch head 861fa708, which has master merged in. The pin moves to #1686's merge commit once it lands. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e limits Review of #1686: - POST /api/agents without ttlS now grants 30 days, or the offer's length if shorter, not the offer's year. - A ttlS past the role's longest grant is refused up front with E_CAP_EXCEEDED {cap: expiresAtMs, max}, so a huge one cannot wrap (18446744073709552 s used to become 384 ms). - agent.list carries longestMs to owners only; an agent's answer leaves it out, as docs/agents.md says. GET /api/agents no longer carries a stray top-level "longestMs": null beside box.longestMs. - The box page falls back per role when box.longestMs is missing: 30 days for an assistant, 14 for Sourceful, 90 for the release check. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
8a7bd9c6e597c4396deb05a5b7cd728f6619f97b on srcfl/ftw master. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…to a year (#27) * feat(oauth): an assistant's tokens may last as long as its grant, up to a year The signed offer asks for a year, the new protocol.AgentMaxMs. The owner picks less in the app or on the box page; both start at 30 days and offer a box no more than its agent.list says it takes, or 30 days when it says nothing. A refresh token lasts at most RefreshTTL, now a year, and never past the grant's end in the box's newest roster. Each refresh now takes that end afresh, so a grant the owner renews keeps its tokens; before, a family died at the end it had when the code was swapped, renewed or not. The access token stays at ten minutes. Moves the go/protocol pin to srcfl/ftw claude/agent-grant-year. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * chore: pin go/protocol at srcfl/ftw claude/agent-grant-year 70c7719c Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(oauth): a renewed grant keeps an idle assistant's tokens Review of #27: a refresh row was clamped to the grant's end when issued, and setGrant moves only the agent row. An assistant last used on day 10 whose owner renewed for a year on day 29 got 400 on day 31. Now a refresh token lasts RefreshTTL (a year) from issue; an agent past its end is still refused at every refresh and every call (agentGone) and swept with its tokens. The access token stays within ten minutes and never past the grant's end. The service also takes no roster end past a year from now. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * chore: pin go/protocol at srcfl/ftw#1686's merge commit 8a7bd9c6e597c4396deb05a5b7cd728f6619f97b on srcfl/ftw master. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Problem
Fredrik, 17 October 2026: "token ska kunna vara längre än 30 dagar kanske också 1 år". An assistant's grant (role
agent) could last at most 30 days.Change
protocol.AgentMaxMsgoes from 30 to 365 days.support(14 days) andrelease(90 days) stay.checkGrantstill refuses anything longer withE_CAP_EXCEEDED {cap: "expiresAtMs", max}.agent.listnow carrieslongestMs: {role: ms}, the longest grant the box takes for each role (protocol.LongestMs()). A newer app reads it and offers an older box no more than it takes.docs/agents.md: the Principals table, theagent.listanswer, a short "Mixed versions" section, the offer'sttlS, and "Numbers in one place".Unchanged: expiry is wall clock and fails closed without a trusted clock; the wall-clock mark still stops a clock set back from reviving a grant; holds still last minutes; durable changes still need the owner's yes.
Mixed versions
An old box does not clamp. It refuses an expiry past 30 days with
E_CAP_EXCEEDED. FTW Cloud builds the offer before the owner picks a home, so it cannot ask each box what it takes. So the fix lives in the clients:agent.list'slongestMs. NolongestMsmeans an older box, and the app offers 30 days at most.One case still refuses rather than clamps: a hand-written
POST /api/agentsto an old box that leavesttlSout gets the offer's year and is refused. No client in the field does that. On a box with this PR, a missingttlSgrants 30 days (or the offer's length if shorter), and attlSpast the role's longest grant is refused up front withE_CAP_EXCEEDED {cap: "expiresAtMs", max}, so a huge one cannot wrap.agent.expiringIt still fires once, in a grant's last day, whatever the length. The words on the phone ("Access ends tomorrow", from the push catalogue that FTW Cloud reads through
go/protocol) say "tomorrow", so an earlier notice would need new text in every copy, and older apps would show the wrong words. Renewing takes one Face ID.TestAYearLongGrantWarnsInItsLastDayholds it.Box page
#1682 merged while this was in progress, so its Access tab is here too. GET /api/agents now carries
box.longestMs(inAgentFacts, filled byboxPageAgents.Facts()). The tab offers 1, 7, 30 and 90 days and a year, never past the offer'sttlSorbox.longestMsfor the role, and still starts at a week, as #1682 chose. Renewing follows the same rule, which also stops it offering a support row 30 days that the box refuses (support ends at 14).The field lives in
AgentFacts, not in the embeddedAgentList, because open #1684 replaces that embedding with fields of its own. A copy of #1684 rebased onto master merges with this branch without conflict (git merge-tree).Seen in a browser against a stub box serving this branch's
web/: the offer card lists 1 day to 1 year with 7 days chosen; Claude's renew lists "1 day from now" to "1 year from now"; Sourceful's renew lists 1 and 7 days.Tests
TestAnAssistantMayHaveAYearAndNoMore: a year goes through; a year and a millisecond is refused, at add and at renewal, withmax= now + a year.TestTheBoxTellsTheLimitsItHolds:LongestMs()matches whatcheckGrantholds: 365, 14 and 90 days.TestAgentListTellsAnOwnerTheLongestGrant: an owner'sagent.listcarrieslongestMs.TestAYearLongGrantWarnsInItsLastDay: no warning on day 0, day 300 or 25 hours before; one warning in the last day.TestTheBoxPageIsToldTheLongestGrant: the box page's facts carrylongestMs.access.test.mjs: a year where the box takes one, 30 days where it says nothing, support renewals capped at its 14 days, a week to start.TestTheBoxPageBoundsALength: nottlSgives 30 days of a year's offer; a year goes through; a year and a second, and 18446744073709552 s, are refused withmax= now + a year.TestTheBoxPageListCarriesNoStrayLongest:GET /api/agentshasbox.longestMsand no top-level copy.agent.listcarries nolongestMs.make verifyandnpm test(760 tests): pass.Changeset
patch: it lengthens an existing choice and adds no new action. Say if you wantminor.Pairs
go/protocolat this branch; move the pin to the merge commit after this merges.longestMsread, 30 days for a box that says nothing).ftw-webapp/contract/registry.yaml) holds no copy of the number, so no contract PR.Merge order: this PR first, then ftw-cloud (pin moved), then the app. Deploy FTW Cloud (srcfl/ftw-cloud#27) before this reaches boxes in a beta, and before the app.
New grants are safe in any order: a newer cloud with older apps and box pages still gives 30 days; a newer box with an older cloud still gets 30-day offers. Renewals are not. The renew pickers (box page
editForm, the app'sGrantView) offer a year from the box'slongestMsalone. An older cloud ends every refresh-token family 30 days after the code swap, whatever the box later says, so with an older cloud a grant renewed for a year still loses its assistant at day 30, and the assistant must connect again. ftw-cloud#27 makes the refresh token follow the grant's end.🤖 Generated with Claude Code