Skip to content

feat(http): session resume via X-Client-Session-Id; client-authoritative reconciliation#71

Merged
manojp99 merged 1 commit into
mainfrom
feat/chat-completions-session-resume
Jun 23, 2026
Merged

feat(http): session resume via X-Client-Session-Id; client-authoritative reconciliation#71
manojp99 merged 1 commit into
mainfrom
feat/chat-completions-session-resume

Conversation

@manojp99

Copy link
Copy Markdown
Collaborator

What this PR does

When the chat-completions client sends X-Client-Session-Id, amplifier-agent now uses a deterministic http-<client_sid> as the amplifier session_id, auto-detects whether this is the first turn or a continuation by checking if the session state dir exists on disk, and passes is_resumed to the existing kernel resume mechanism (same primitive the CLI face's --resume flag uses). One opencode conversation = one amplifier session — unified audit trail, persistent hook state across turns, append-mode events.jsonl.

Client-authoritative transcript reconciliation in src/amplifier_agent_http/_reconciler.py. Since the chat-completions wire is stateless and the client sends full history every turn, on divergence between stored and incoming the client wins by fiat — we persist the client's view over our stored copy without any rewind ceremony. Sufficient for opencode and any well-behaved OpenAI-compatible client. No new event types introduced.

Changes

File What changed
src/amplifier_agent_http/_reconciler.py New — HTTP-face-private reconcile_client_history(). Persists client messages to SessionStore, returns messages unchanged.
src/amplifier_agent_http/routes/chat_completions.py Initializes client_session_id_clean before the workspace block; adds sid + is_resumed determination; calls reconciler; passes session_id + is_resumed to run_chat_turn.
src/amplifier_agent_http/_session_runner.py Adds is_resumed: bool = False to run_chat_turn signature; threads it through to prepared.create_session().
tests/http/test_reconciler.py New — 8 tests covering reconciler unit behavior and route integration.
CHANGELOG.md Added two entries under [Unreleased] > Added.

Backward compatibility

Clients that do not send X-Client-Session-Id get a random sid per turn (via run_chat_turn) and is_resumed=False — identical to current behavior.

How to smoke test

  1. Start the server: amplifier-agent serve chat-completions --api-key test
  2. Send turn 1 with the header:
    curl -s -X POST http://localhost:8000/v1/chat/completions \
      -H 'Authorization: Bearer test' \
      -H 'Content-Type: application/json' \
      -H 'X-Client-Session-Id: my-smoke-test' \
      -d '{"model": "<your-model>", "messages": [{"role": "user", "content": "Say hi"}]}' | head -5
  3. Confirm a session dir was created:
    ls ~/.amplifier-agent/state/workspaces/*-my-smoke-test/sessions/http-my-smoke-test/
    # Should show: transcript.jsonl  metadata.json
  4. Send turn 2 with the same header and different content. Check that the stored transcript was updated (client wins).
  5. Without the header, confirm the session dir is not created (legacy path unchanged).

Validation

  • uv run pytest tests/http/24 passed (16 pre-existing + 8 new)
  • uv run pytest tests/config/47 passed
  • uv run pytest tests/cli/237 passed, 1 pre-existing env-var failure on main (unchanged)
  • uv run ruff check src/ tests/ — clean
  • uv run pyright src/ — 1 pre-existing error in single_turn.py (not in this PR's files)

…ive reconciliation

When the chat-completions client sends X-Client-Session-Id, treat it as
authoritative for the amplifier session_id (`http-<client_sid>`).  Auto-
detect first-turn vs continuation by state_dir.exists().  Pass is_resumed
into the existing make_turn_handler — same primitive `amplifier-agent run
--resume` uses.

Add HTTP-face-private reconcile_client_history() that persists the
client's full-history view as authoritative.  No divergence detection,
no new event types, no ceremony — client wins by fiat.

Backward compatible: clients without the header get a random sid per
turn as before.

🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier)

Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
@manojp99 manojp99 marked this pull request as ready for review June 23, 2026 01:33
@manojp99 manojp99 merged commit 54a7cfa into main Jun 23, 2026
2 of 3 checks passed
manojp99 added a commit that referenced this pull request Jun 23, 2026
opencode sends X-Session-Id by default via Vercel AI SDK -- recognize
it as a fallback for X-Client-Session-Id so PR #71's session-resume
chain works for opencode with zero config.

Also drop the workspace-suffix mechanism. Per-client distinction is at
the session_id level; workspace stays at server-process scope so hook
state (context-intelligence) shares cleanly across client sessions.

🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier)

Co-authored-by: Manoj Prabhakar Paidiparthy <mpaidiparthy@microsoft.com>
Co-authored-by: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
manojp99 added a commit that referenced this pull request Jun 23, 2026
Cuts v0.9.0 release with chat-completions session resume, client-authoritative
reconciliation, and zero-config opencode session continuity via X-Session-Id header.

See CHANGELOG.md [0.9.0] for details: PR #71, #72, #73.

Generated with [Amplifier](https://github.com/microsoft/amplifier)

Co-authored-by: Manoj Prabhakar Paidiparthy <mpaidiparthy@microsoft.com>
Co-authored-by: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
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.

1 participant