Skip to content

feat(session): let a session share expire - #2987

Merged
EItanya merged 4 commits into
kagent-dev:mainfrom
QuentinBisson:upstream/session-share-expiry
Oct 2, 2026
Merged

EItanya merged 4 commits into
kagent-dev:mainfrom
QuentinBisson:upstream/session-share-expiry

Conversation

@QuentinBisson

@QuentinBisson QuentinBisson commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

Problem

A session share's token grants access until the share is revoked or the session is deleted: CreateSessionShareRequest takes no lifetime, and GetSessionShareByTokenHash checks nothing but the hash and the session's state. A client that keeps one share per conversation, for the people the owner lets in, has no way to bound it once the conversation is abandoned. A READ_WRITE share can also rename, suspend and delete the session.

Change

  • CreateSessionShareRequest.ttl is optional and must be positive when set. The share records expires_at in a nullable session_share.expires_at column, added to the unreleased 000001 baseline.
  • Token resolution treats an expired share like an unknown token, so both A2A transports refuse it. ListSessionShares still returns an expired share, so the owner can revoke it.
  • KAGENT_SESSION_SHARE_MAX_TTL (controller.sessionShareMaxTTL) lets admins cap share lifetime. A longer ttl is refused with InvalidArgument, and a share created without ttl receives the maximum. The default 0 keeps shares unbounded, so a share created without ttl behaves as before.
  • The controller's clock sets expires_at and the database clock checks it. The skew between the two (NTP, milliseconds) is accepted.

The migration immutability check fails because the column is folded into 000001, as its header asks until release (same as #2970).

@github-actions github-actions Bot added the enhancement New feature or request label Sep 28, 2026
QuentinBisson added a commit to giantswarm/kagent-upstream that referenced this pull request Sep 28, 2026
Signed-off-by: QuentinBisson <quentin@giantswarm.io>
@QuentinBisson
QuentinBisson marked this pull request as ready for review September 29, 2026 09:22

@EItanya EItanya left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This feature is looking great! Do you think you can add a ceiling value to the server so that admins can set a maximum valid time

@QuentinBisson

Copy link
Copy Markdown
Contributor Author

I definitely can, thanks for the review :)

@QuentinBisson
QuentinBisson force-pushed the upstream/session-share-expiry branch from 59b1965 to fd8bcda Compare September 29, 2026 15:09
@github-actions github-actions Bot added enhancement New feature or request and removed enhancement New feature or request labels Sep 29, 2026
@QuentinBisson

Copy link
Copy Markdown
Contributor Author

I pushed the new maximum valid time :)

QuentinBisson added a commit to giantswarm/kagent-upstream that referenced this pull request Sep 29, 2026
Signed-off-by: QuentinBisson <quentin@giantswarm.io>
Comment thread go/core/internal/database/shares.go Outdated
Comment on lines +145 to +150
func sameExpiry(payload *timestamppb.Timestamp, column *time.Time) bool {
if payload == nil || column == nil {
return payload == nil && column == nil
}
return payload.AsTime().Equal(*column)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we really need this? How could these get out of sync, we have many times we do something similar and we don't have this check

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right, this field is only set when we create a share and cannot be updated unless someone does it in the database. I'll simplify this

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The expiry is only stored in the column now, so sameExpiry and the truncation are gone.

@github-actions github-actions Bot added enhancement New feature or request and removed enhancement New feature or request labels Sep 29, 2026
not_in: 0
}];
// How long the share's token grants access, from its creation. Unset means
// until the share is revoked or the session deleted.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unset gets the server max when one is configured

if ttl < 0 {
return nil, "", serviceerrors.NewInvalidArgument("share ttl must be positive", nil)
}
if s.shareMaxTTL > 0 {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

enabling the cap later leaves older no-ttl shares unbounded

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's ok, we're pre-alpha

@EItanya
EItanya enabled auto-merge September 29, 2026 21:27
QuentinBisson added a commit to giantswarm/kagent-upstream that referenced this pull request Oct 1, 2026
Signed-off-by: QuentinBisson <quentin@giantswarm.io>
CreateSessionShareRequest takes an optional ttl. The share records
expires_at, and token resolution treats an expired share like an unknown
token. The owner still lists an expired share so it can be revoked.

Signed-off-by: QuentinBisson <quentin@giantswarm.io>
KAGENT_SESSION_SHARE_MAX_TTL (controller.sessionShareMaxTTL) sets the
longest ttl a share may request. A longer ttl is refused, and a share
created without one receives the maximum. Zero, the default, leaves
shares unbounded; a negative value fails controller startup.

Signed-off-by: QuentinBisson <quentin@giantswarm.io>
Signed-off-by: QuentinBisson <quentin@giantswarm.io>
Signed-off-by: QuentinBisson <quentin@giantswarm.io>
auto-merge was automatically disabled October 2, 2026 07:26

Head branch was pushed to by a user without write access

@QuentinBisson
QuentinBisson force-pushed the upstream/session-share-expiry branch from 50235d3 to f9ca91b Compare October 2, 2026 07:26
QuentinBisson added a commit to giantswarm/kagent-upstream that referenced this pull request Oct 2, 2026
Signed-off-by: QuentinBisson <quentin@giantswarm.io>
@QuentinBisson

Copy link
Copy Markdown
Contributor Author

Rebased onto main and pushed one change:

  • The ttl proto comment now says that an unset ttl gets the server maximum when one is configured (thanks @moezdil).

I also added a note to the PR body: the controller's clock sets expires_at and Postgres now() checks it. I'm accepting that skew because NTP keeps it in milliseconds and share TTLs are much longer.

@EItanya, one question before this merges: v1.0.0-alpha6 and alpha7 shipped 000001 without session_share.expires_at. The header still says to fold changes into 000001 until release, so I kept it there. But a database created on an alpha would not get the column, and share lookups would then fail with a missing-column error. Is folding still right after the alphas, or should this be 000002?

@github-actions github-actions Bot removed the enhancement New feature or request label Oct 2, 2026
@github-actions github-actions Bot added the enhancement New feature or request label Oct 2, 2026
QuentinBisson added a commit to giantswarm/kagent-upstream that referenced this pull request Oct 2, 2026
Signed-off-by: QuentinBisson <quentin@giantswarm.io>
@EItanya
EItanya added this pull request to the merge queue Oct 2, 2026
Merged via the queue into kagent-dev:main with commit 4dce53d Oct 2, 2026
32 of 33 checks passed
@QuentinBisson
QuentinBisson deleted the upstream/session-share-expiry branch October 2, 2026 13:33
alexdarbyshire pushed a commit to alexdarbyshire/kagent that referenced this pull request Oct 5, 2026
## Problem

A session share's token grants access until the share is revoked or the
session is deleted: `CreateSessionShareRequest` takes no lifetime, and
`GetSessionShareByTokenHash` checks nothing but the hash and the
session's state. A client that keeps one share per conversation, for the
people the owner lets in, has no way to bound it once the conversation
is abandoned. A `READ_WRITE` share can also rename, suspend and delete
the session.

## Change

- `CreateSessionShareRequest.ttl` is optional and must be positive when
set. The share records `expires_at` in a nullable
`session_share.expires_at` column, added to the unreleased `000001`
baseline.
- Token resolution treats an expired share like an unknown token, so
both A2A transports refuse it. `ListSessionShares` still returns an
expired share, so the owner can revoke it.
- `KAGENT_SESSION_SHARE_MAX_TTL` (`controller.sessionShareMaxTTL`) lets
admins cap share lifetime. A longer `ttl` is refused with
`InvalidArgument`, and a share created without `ttl` receives the
maximum. The default `0` keeps shares unbounded, so a share created
without `ttl` behaves as before.
- The controller's clock sets `expires_at` and the database clock checks
it. The skew between the two (NTP, milliseconds) is accepted.

The migration immutability check fails because the column is folded into
`000001`, as its header asks until release (same as kagent-dev#2970).

---------

Signed-off-by: QuentinBisson <quentin@giantswarm.io>
QuentinBisson pushed a commit to giantswarm/kagent-upstream that referenced this pull request Oct 6, 2026
| 2026-10-05 20:30 | `dd3b240` | `bf8afa5` (2026-10-05) | `a5186dd` → `f7bf3da` | 102 own, as kagent-dev#3019/kagent-dev#3020/kagent-dev#3018/kagent-dev#3066 have them where the line had its own form (`Harness.spec.substrate.egress` and its wildcard fix → `Agent.spec.egress`; the four limits commits → kagent-dev#3020; the caller credential and its path fix → kagent-dev#3018, the two forwarder fixes kept on top; #136/#137 shared turn → kagent-dev#3066); the Substrate pin on the line's 1.4.0 (kagent-dev/substrate v0.3.0-alpha3, giantswarm/substrate `e27ae953`); the 2026-09-30 align-files commit (auto-release, PR title check, cliff.toml) re-carried after the re-pin dropped it, with the candidate counter of giantswarm/devctl#2498 applied by hand | FORK.md "Dropped at the 2026-10-05 re-pin": the discovery-label commits (kagent-dev#2752), AUTH_MODE wiring (kagent-dev#2971), pair-collection comparison (kagent-dev#2952), gRPC log trace ids (kagent-dev#2929), the grpc pin, the ActorTemplate rejection pair (kagent-dev#2994), the AgentInstance share expiry (kagent-dev#2987), the three gateway dispatch fixes (kagent-dev#2939), the 1.0 schema migration and its CI upgrade check (replaced by the 1.x refusal); owed: the boot-bound golden retry (`ebe49f0f`) and the not-resumable directive reader (`7eabf64f`) | by hand — HeraldBot's hand-over [#159](#159) updated in place; [CI Build 37430773899](https://github.com/giantswarm/kagent-upstream/actions/runs/37430773899) on the candidate, [37434519929](https://github.com/giantswarm/kagent-upstream/actions/runs/37434519929) on the pushed branch; release `v1.3.0` (2026-10-06T08:24Z, promoted from `v1.3.0-rc.16`, which the force-push cut at 08:12Z) |
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants