Skip to content

Feature/session persistence - #407

Open
utkarshkatiyar1 wants to merge 3 commits into
initializ:mainfrom
utkarshkatiyar1:feature/session-persistence
Open

Feature/session persistence#407
utkarshkatiyar1 wants to merge 3 commits into
initializ:mainfrom
utkarshkatiyar1:feature/session-persistence

Conversation

@utkarshkatiyar1

Copy link
Copy Markdown

Type of Change

  • Bug fix
  • New feature
  • Enhancement / refactor
  • New skill
  • Documentation
  • CI / build

Description

General Checklist

  • Tests pass for affected modules (go test ./...)
  • Code is formatted (gofmt -w)
  • Linter passes (golangci-lint run)
  • go vet reports no issues
  • No new egress domains added without justification

Skill Contribution Checklist

  • forge skills validate passes with no errors
  • forge skills audit reports no policy violations
  • egress_domains lists every domain the skill contacts
  • No secrets or credentials are hardcoded
  • SKILL.md includes ## Tool: sections with input/output tables
  • Skill tested locally with expected input/output

Related Issues

@initializ-mk initializ-mk 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.

Review — session persistence + refresh recovery

Nice work on this — it's a real, well-scoped fix for the "refresh loses my message / kills the turn" problem, and the tests are a good regression guard. I traced it end to end; CI is green.

Verified:

  • The fix reaches the real path. UI chat goes through tasks/sendSubscribeExecuteStream, which delegates to Execute, so the persistSession calls you added to Execute fire on the streaming flow (not just the direct-Execute path the tests drive). Good — this is the thing that could easily have been a gap.
  • Storage is safe. persistSessionMemoryStore.Save sanitizes the task ID (separators can't traverse), writes atomically (temp+fsync+rename) under a mutex — so the new persist-on-cancel call sites can't corrupt or escape.
  • The detach is sound and mirrors the approach in #402: context.WithoutCancel(r.Context()) + the clientGone drain keep the agent turn alive and let it finish + persist after a refresh, bounded by the 10-min timeout.
  • handleSessionStatus is authed (reuses loadAgentToken) and degrades gracefully to {state:"unknown"}.

Three non-blocking notes inline. Also — could you fill in the PR description (what/why + link the refresh bug)? It'll help the maintainers' review.

Comment thread forge-ui/chat.go
}

// POST to the agent's A2A endpoint.
agentCtx, cancel := context.WithTimeout(context.WithoutCancel(r.Context()), agentCallTimeout)

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.

MED–LOW — concurrent turns on the same session can clobber (last-write-wins). Because this detaches the agent call from the request, a refreshed-but-still-running turn keeps going. The resume flow polls status but doesn't lock out sending, so if the user sends again before the old turn settles, two Execute goroutines run on the same task ID and both persistSession to the same file. MemoryStore.Save is mutexed + atomic so the file won't corrupt — but the later Save overwrites the earlier turn's messages (logical loss), since each Execute persists its own mem.

Worth confirming the agent already serializes/rejects a second tasks/send for a task that's still working — if it doesn't, this is reachable. Cheapest UI-side guard: disable send while fetchSessionStatus reports working/submitted (you already expose exactly that signal via the new status endpoint).

Comment thread forge-ui/chat.go
"github.com/initializ/forge/forge-core/auth"
)

const agentCallTimeout = 10 * time.Minute

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.

Minor (bound): with the detach, this 10-min timeout becomes the sole upper bound on a UI-initiated turn (a client disconnect no longer ends it), and the handler holds the agent connection/goroutine draining SSE until the agent finishes or this fires. That's bounded and intentional (same trade-off as #402), just worth being a conscious default — if agents legitimately run long tool chains past 10 min, the turn is cut (the persist-on-cancel you added does save the partial state, which is the right mitigation).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants