Anna App agent session lifecycle issues: expired sessions cannot be released, TTL does not refresh, and UUID cannot be reused after local dev restart

We tested the frontend agent.session lifecycle in a local Anna App development environment and found several issues that may affect both development and real usage:

  • Expired sessions cannot be deleted and continue to occupy the active session quota.
  • The session expiration time appears to be calculated from creation time, not refreshed by later activity.
  • After restarting the local anna-app dev server, an existing app_session_uuid cannot be used again, even before it expires.

Test Background

We created a dedicated Anna App demo named test-session-app to test the session lifecycle.

This demo only uses the frontend raw RPC APIs. It does not use the high-level helper.

The core calls are:

await anna.agent.session.create({ submode: "auto" });

await anna.agent.session.run({
  app_session_uuid,
  content,
});

await anna.agent.session.delete({
  app_session_uuid,
});

For session.run, we handle streaming using the raw RPC flow: first call agent.session.run to get { stream_id, run_id }, then listen to rpc.stream events and collect frames by stream_id.

The page keeps a list of currently held sessions and persists app_session_uuid values to localStorage, so we can test whether the same session can still be used after page refresh or local dev server restart.

We only persist app_session_uuid. We do not persist app_session_token, because the frontend API does not expose it.

Issue 1: Expired sessions cannot be deleted, causing active session quota to remain occupied

Reproduction steps:

  1. Start the test app in a local anna-app dev environment.
  2. Create 5 agent sessions.
  3. Before they expire, run “clean up all”. All 5 sessions can be deleted successfully and return { deleted: true }.
  4. Create another 5 sessions.
  5. Wait longer than the returned expires_in: 600 seconds.
  6. Try to call session.delete for these 5 sessions.
  7. All delete requests fail. After that, creating a new session returns HTTP 429, saying the active session limit for the same app/executa has been reached.

Relevant logs:

session.create result
{"app_session_uuid":"aps_p4olz6jalwla7olqvldelp","expires_in":600}

session.delete failed
{
  "app_session_uuid":"aps_p4olz6jalwla7olqvldelp",
  "message":"HTTP 401: {\"detail\":{\"code\":-32001,\"errorCode\":\"APP_NOT_GRANTED\",\"message\":\"invalid app_session_token: Signature has expired.\"}}"
}

session.create failed
session.mint failed: HTTP 429
{"detail":"dev mode: 已达同一 app/executa 活跃 session 上限 (5);请先 delete 旧 session 或 revoke 后重试。"}

Actual behavior:

After a session expires, session.delete fails because the token has expired. However, these sessions still count toward the active session limit, so no new sessions can be created.

Expected behavior:

Expired sessions should satisfy at least one of these behaviors:

  1. Expired sessions should be automatically released from the active session quota.
  2. session.delete / revoke should still be able to release the session after the app session token expires.
  3. There should be a cleanup/revoke API that does not depend on an expired app session token.
  4. If expired sessions still count toward quota, this behavior should be clearly documented, including the server-side cleanup timing.

The current issue is that the session has expired and can no longer be used by the client, but it also cannot be deleted, while the server still counts it as active. This can leave the local dev environment stuck until an unknown server-side cleanup happens or some external management tool is used.

Issue 2: Session expiration appears to be fixed from creation time, not refreshed after use

Reproduction steps:

  1. Create a session. It returns expires_in: 600.
  2. About 5 minutes later, call session.run. The request succeeds and the stream returns normally.
  3. About another 5 minutes later, call session.delete.
  4. session.delete fails because the token has expired.

Relevant logs:

[15:29:00] session.create result
{"app_session_uuid":"aps_tzm343vlftwifvyygrf5mc","expires_in":600}

[15:34:25] session.run started
{"app_session_uuid":"aps_tzm343vlftwifvyygrf5mc"}

[15:34:37] session.run completed

[15:40:29] session.delete started
{"app_session_uuid":"aps_tzm343vlftwifvyygrf5mc"}

[15:40:31] session.delete failed
"invalid app_session_token: Signature has expired."

Actual behavior:

After session.run succeeds, the session lifetime is not extended. It still expires roughly 10 minutes after creation.

We would like to confirm the intended official semantics here.

If expires_in is a fixed TTL, then the current behavior is understandable, but the documentation should clearly state that an agent session expires based on session.create time, and later session.run calls do not refresh the lifetime.

If the intended behavior is closer to an active session or idle timeout, then we would expect a successful session.run to refresh the session lifetime. Otherwise, in long-running or multi-turn interaction scenarios, a session may expire while the user is still actively using it.

Also, session.create currently returns expires_in, but we do not see updated TTL or expiry information after later runs. If the TTL is fixed, it would be useful to expose expires_at so the frontend can display and reason about expiration accurately.

Issue 3: After restarting local anna-app dev, the same app_session_uuid cannot be used again

Reproduction steps:

  1. Create a session and get its app_session_uuid.
  2. Restart the local anna-app dev backend before the session expires.
  3. The frontend restores the saved app_session_uuid from localStorage.
  4. Call session.run.
  5. The call fails with no cached session.

Relevant logs:

session.create result
{"app_session_uuid":"aps_ob6mvxdr33robcdhhwffoo","expires_in":600}

session.run started
{"app_session_uuid":"aps_ob6mvxdr33robcdhhwffoo"}

session.run failed
{"message":"no cached session"}

After checking the local @anna-ai/cli harness implementation, our understanding is that anna-app dev keeps an in-memory mintedAgent map from app_session_uuid to app_session_token.

Both session.run and session.delete depend on this in-memory cache to retrieve the token. Once the local dev server restarts, the map is lost. Even if the server-side session has not expired yet, the frontend cannot continue using it with only the app_session_uuid.

If this is an intended limitation, it would be helpful to document it clearly:

  • app_session_uuid alone is not a resumable credential.
  • The frontend cannot restore a session across dev harness restarts using only app_session_uuid.
  • The session object returned by runtime.agent.session({ submode: "auto" }) is only valid within the current runtime/harness lifecycle.
  • If cross-refresh, cross-process, or cross-program session reuse is needed, an official session.resume/recover API or another safe persistence mechanism is required.

Suspected Causes

Based on the observed behavior, there may be two separate causes:

  1. Server-side active session quota release seems to depend on explicit delete/revoke, but delete depends on a still-valid app_session_token. Once the token expires, the client loses permission to delete the session, while the server-side active quota is not released.
  2. The local dev harness only stores app_session_uuid -> app_session_token in memory. It does not persist this mapping, and it does not appear to have a way to recover the token from the server using only app_session_uuid. Therefore, restarting the local dev server makes existing session UUIDs unusable.

Questions for the Anna team

  1. Is expires_in for agent sessions a fixed TTL or an idle timeout?
  2. Should a successful session.run refresh the session expiration time?
  3. Should expired sessions continue to count toward the active session quota?
  4. If the session token has expired, how should the client release that session?
  5. Are there plans to provide a session.resume, session.revoke, or management cleanup API so an app can recover or release a session by app_session_uuid?
  6. In local anna-app dev, is app_session_uuid intended to be reusable across dev server restarts?

Impact

These behaviors affect how agent sessions can be integrated in practice:

  • The frontend cannot reliably hold sessions for longer workflows.
  • Once a session expires, it may become impossible to release manually.
  • The local dev environment can easily hit the active session limit and get stuck.
  • Persisting only app_session_uuid is not enough to restore a session.
  • Multi-session testing, page refresh recovery, multi-turn workflows, and long editing sessions are all affected.

Could the Anna team confirm whether these behaviors are expected? If they are expected, we would appreciate clearer documentation and recommended practices. If they are not expected, the highest-priority issue seems to be that expired sessions cannot be released but still occupy active session quota.

Hi HappyLight,

First off — thank you for such a thorough, well-instrumented report. The reproduction steps, timestamped logs, and your root-cause analysis of the in-memory [mintedAgent] map were spot-on and made this very easy to act on. This is exactly the kind of feedback that makes the platform better.

Good news: all three issues are now fixed and shipped. Your diagnosis was correct on every point, so rather than documenting the old behavior as “intended,” we redesigned the agent session lifecycle. Here’s what changed, mapped to your questions:

1 & 2 — TTL semantics and refresh on activity
[expires_in] is now an idle timeout (sliding window), not a fixed-from-creation TTL. A successful [session.run] (and other valid activity) now refreshes the session lifetime. There’s still an absolute lifetime cap so sessions can’t live forever, but active sessions no longer expire out from under a user mid-conversation. Responses now also expose richer lifecycle metadata ([expires_at], [max_lifetime_at], idle_ttl_seconds) so your frontend can display and plan expiry accurately.

3 — Expired sessions and the active quota
Expired sessions no longer occupy the active quota. They’re reclaimed opportunistically on the next [session.create] and also swept by a background cleanup job, so the local dev environment can no longer get stuck at the active-session limit.

4 & 5 — Releasing/recovering a session without a valid token
This was the core problem: release used to depend on a still-valid [app_session_token]. There are now token-free management paths that authenticate by your identity (not the short-lived session token):

  • [agent.session.refresh] — re-mints a fresh token and slides the idle window using only the [app_session_uuid]. This makes the UUID a recoverable handle.
  • Token-free revoke (single, and bulk/expired-only) — releases a session even when its token is long gone.
  • Session listing — to rediscover sessions whose in-memory handle was lost.

6 — UUID reuse across anna-app dev restarts
Fixed. The dev harness now persists the UUID and recovers a live token via the refresh path on demand, so an existing [app_session_uuid] keeps working across a dev server restart (as long as the underlying session hasn’t genuinely expired or been revoked). We also distinguish “session is really gone” from “token expired — just refresh and retry” via separate error codes, so clients can handle each correctly.

These landed in matrix-nexus 1.1.0-beta.42 along with bumped platform SDK / CLI packages (the new methods are additive, so older clients degrade gracefully). Please update @anna-ai/cli and @anna-ai/app-runtime to pick them up, and let us know how the multi-session / refresh-recovery flows feel in practice.

Thanks again for pushing on this — really appreciate the detail. Happy building!