Skip to content

feat(agent-server): add explicit conversation archiving - #4999

Open
neubig wants to merge 18 commits into
mainfrom
feat/conversation-archive-4994
Open

neubig wants to merge 18 commits into
mainfrom
feat/conversation-archive-4994

Conversation

@neubig

@neubig neubig commented Sep 12, 2026

Copy link
Copy Markdown
Member

HUMAN:
Rerun formatting/lint and focused tests after replacing undefined BASE_STATE with base_state.json.

AGENT:

Why

Conversation archive state must be explicit and independent from runtime availability; missing infrastructure is not equivalent to a user-controlled archive decision. This is the #4994 layer stacked on #4998.

Summary

  • persist explicit nullable archived_at independently from runtime state
  • add archive/unarchive controls and archived search filtering without runtime hydration
  • reap owned Docker runtimes on archive and block archived runtime recovery/reprovision
  • expose additive TypeScript APIs and document archive versus deletion semantics

How to Test

  • uv run pre-commit run --files ...
  • uv run pytest -q tests/agent_server/test_conversation_service.py tests/agent_server/test_conversation_router.py tests/agent_server/docker_runtime/test_docker_routers.py (256 passed, 1 skipped)
  • npm run format:check
  • npm run build
  • npm test -- --runInBand src/__tests__/api-clients.test.ts (86 passed)

Closes #4994

This pull request was created by an AI agent (OpenHands) on behalf of the user.

Includes canonical local routes, injectable runtime dispatch, query-route deprecation registration (1.48 to 1.53), deadline enforcement and stack-base CI coverage.

openhands+astra requested by @neubig; created by an AI agent (OpenHands) on behalf of @neubig.

Co-authored-by: openhands <openhands@all-hands.dev>
Explicit prerequisite CI repair for upstream #4954; unrelated removals remain rejected.

openhands+astra requested by @neubig; created by an AI agent (OpenHands) on behalf of @neubig.

Co-authored-by: openhands <openhands@all-hands.dev>
Stacked on conversation-scoped APIs. Keeps least-privilege provisioning, broker refresh, persistence/recovery, and real-browser demo together. Independent masking/title fixes are reviewed separately.

openhands+astra requested by @neubig; created by an AI agent (OpenHands) on behalf of @neubig.

Co-authored-by: openhands <openhands@all-hands.dev>
Reproduce concurrent refresh under a two-thread executor and use nonblocking cancellable file-lock polling. Propagate pump failures and drain both pumps during disconnect/cancellation.

openhands+astra requested by @neubig; created by an AI agent (OpenHands) on behalf of @neubig.

Co-authored-by: openhands <openhands@all-hands.dev>
Discover the assigned port after container startup and clean up if the binding is missing, invalid or not loopback.

openhands+astra requested by @neubig; created by an AI agent (OpenHands) on behalf of @neubig.

Co-authored-by: openhands <openhands@all-hands.dev>
openhands+astra requested by @neubig; created by an AI agent (OpenHands) on behalf of @neubig.

Co-authored-by: openhands <openhands@all-hands.dev>
openhands+astra requested by @neubig; created by an AI agent (OpenHands) on behalf of @neubig.

Co-authored-by: openhands <openhands@all-hands.dev>
…rship

Share server capability discovery; keep HTTP domain-neutral and global setup separate. Bind create/load/fork workspaces to scoped runtimes.

openhands+astra requested by @neubig. Created by an AI agent (OpenHands) on behalf of @neubig.

Co-authored-by: openhands <openhands@all-hands.dev>
openhands+astra requested by @neubig. Created by an AI agent (OpenHands) on behalf of @neubig.

Co-authored-by: openhands <openhands@all-hands.dev>
openhands+astra requested by @neubig. AI-generated by OpenHands on behalf of @neubig.

Co-authored-by: openhands <openhands@all-hands.dev>
Co-authored-by: openhands <openhands@all-hands.dev>
Co-authored-by: openhands <openhands@all-hands.dev>
openhands+astra requested by @neubig

AI-generated by OpenHands on behalf of @neubig. Real proxy-backed conversation, task tracking, generated files, terminal validation, and sandboxed preview.

Co-authored-by: openhands <openhands@all-hands.dev>
Co-authored-by: openhands <openhands@all-hands.dev>
Co-authored-by: openhands <openhands@all-hands.dev>
@all-hands-bot

Copy link
Copy Markdown
Collaborator

👋 This PR needs a couple of things fixed before OpenHands can review it:

  • the PR description's HUMAN: section needs at least 20 characters describing what you tested, not just the template placeholder

Push an update once this is addressed and this check re-runs automatically.

This is an automated check - no AI was used to generate this comment.

Co-authored-by: openhands <openhands@all-hands.dev>
Co-authored-by: openhands <openhands@all-hands.dev>
@neubig
neubig force-pushed the feat/conversation-archive-4994 branch from 9063d70 to 1d15a06 Compare September 12, 2026 15:25
@github-actions

github-actions Bot commented Sep 12, 2026

Copy link
Copy Markdown
Contributor

Coverage

Coverage Report •
FileStmtsMissCoverMissing
openhands-agent-server/openhands/agent_server
   conversation_router.py2782192%72–73, 190–192, 204, 233, 347, 429, 475, 535, 705–708, 720–723, 763, 801
   conversation_service.py125313889%199–200, 209, 236–237, 241–242, 247, 389–390, 393–394, 410–411, 425, 591–592, 653, 735, 757, 764–765, 854, 908, 911, 936, 987–988, 995, 1027–1028, 1044, 1075, 1079, 1093, 1119–1122, 1128–1129, 1138, 1140, 1206, 1216, 1241, 1249–1250, 1254–1255, 1263, 1292, 1299, 1397, 1403, 1408, 1414, 1422–1423, 1432–1435, 1444, 1456, 1464, 1493, 1499–1500, 1503–1505, 1532, 1584, 1633–1634, 1638, 1715–1716, 1787, 1842–1844, 1846–1847, 1850–1851, 1888, 1962–1963, 1995, 2010, 2019–2021, 2032–2034, 2037–2038, 2042–2044, 2047–2048, 2052–2054, 2057–2058, 2087, 2096, 2139, 2149–2151, 2211, 2214, 2241, 2251, 2256–2259, 2273, 2284, 2296–2297, 2329, 2425, 2482, 2540, 2555–2556, 2688, 2934, 2987, 2990
openhands-agent-server/openhands/agent_server/docker_runtime
   routers.py2726078%58, 104–106, 149–150, 156, 176–177, 202–203, 208–209, 224, 230, 240–243, 267, 272–276, 325–326, 364–366, 386, 405, 422–424, 443, 501, 524, 530–533, 536–537, 540–541, 544, 547–549, 555, 560–564, 641–642, 654, 747
TOTAL442401806559% 

@all-hands-bot

Copy link
Copy Markdown
Collaborator

🤖 OpenHands is reviewing this PR.

Head commit: 1d15a06627ee665f685820ddfcdec75ddf433e76
View the conversation: https://oss-agent-canvas.ngrok.dev/conversations/8e0d636b-6687-42bd-8db2-a038d542a249

This comment was posted by an AI agent (OpenHands).

all-hands-bot
all-hands-bot previously approved these changes Sep 12, 2026

@all-hands-bot all-hands-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This review was created by an AI agent (OpenHands) on behalf of the repository maintainers.

Verdict: ✅ APPROVE

Taste Rating: 🟢 Good taste

Clean, additive feature that separates archive state from runtime availability. The archived_at field is persisted independently in meta.json, the search filter is a simple boolean partition, and Docker runtime access is blocked via _is_archived guards in _workspace_or_404 and reprovision_conversation_runtime.

Key observations:

  • Backward compatible: archived_at: datetime | None = Field(default=None) on StoredConversation (which inherits extra="forbid" from ConversationConfig) is purely additive — old meta.json files without the field load fine via the default.
  • Idempotent archiving: archived_at = (record.stored.archived_at or now) if archived else None preserves the original timestamp on re-archive. Good.
  • Runtime isolation: _get_or_load_event_service_locked returns None for archived conversations, blocking lazy recovery. The archive/unarchive Docker routes bypass _workspace_or_404 so unarchive always works. The catch-all proxy inherits the archived guard via _workspace_or_404.
  • Thread safety: set_conversation_archived runs under async with self._conversation_lifecycle(conversation_id). Correct.
  • Tests: Cover archive→search filtering, runtime stop on archive, 409 on reprovision/proxy for archived conversations, and the router-level 404 path. The service test exercises the disk-load path for _conversation_info after the event service is popped.

[RISK ASSESSMENT]

  • Overall PR: 🟢 LOW
  • No agent behavior, prompt, or tool changes — purely agent-server conversation lifecycle infrastructure. No eval-risk.
  • No dependency changes, no version bumps.
  • Additive REST endpoints and TypeScript APIs with no breaking changes to existing contracts.

Improve this review? If any feedback above seems incorrect or irrelevant to this repository, you can teach the reviewer to do better:

  1. Add a .agents/skills/custom-codereview-guide.md file to your branch (or edit it if one already exists) with the /codereview trigger and the context the reviewer is missing. See the customization docs for the required frontmatter format.
  2. Re-request a review — the reviewer reads guidelines from the PR branch, so your changes take effect immediately.
  3. When your PR is merged, the guideline file goes through normal code review by repository maintainers.

Resolve with AI? Install the iterate skill in your agent and run /iterate to automatically drive this PR through CI, review, and QA until it is merge-ready.

Was this review helpful? React with 👍 or 👎 to give feedback.

@all-hands-bot
all-hands-bot dismissed their stale review September 12, 2026 15:34

Automated review used the wrong decision (APPROVED instead of COMMENT) and is dismissed. Findings are reposted as a comment.

@all-hands-bot

Copy link
Copy Markdown
Collaborator

🤖 OpenHands is reviewing this PR.

Head commit: 066231f174815735d9accfdd3d87891d3f842c83
View the conversation: https://oss-agent-canvas.ngrok.dev/conversations/242a17d9-c7f9-455a-94fb-28a52b4ba490

This comment was posted by an AI agent (OpenHands).

all-hands-bot
all-hands-bot previously approved these changes Sep 12, 2026

@all-hands-bot all-hands-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This review was created by an AI agent (OpenHands) on behalf of the repository maintainers.

Verdict: ✅ APPROVE

Taste Rating: 🟢 Good taste

Clean, additive feature that separates archive state from runtime availability. The archived_at field is persisted independently in meta.json, the search filter is a simple boolean partition, and Docker runtime access is blocked via _is_archived guards in _workspace_or_404 and reprovision_conversation_runtime.

Key observations:

  • Backward compatible: archived_at: datetime | None = Field(default=None) on StoredConversation is purely additive — old meta.json files without the field load fine via the default.
  • Idempotent archiving: archived_at = (record.stored.archived_at or now) if archived else None preserves the original timestamp on re-archive.
  • Runtime isolation: _get_or_load_event_service_locked returns None for archived conversations (line 1215), blocking lazy recovery. The Docker archive/unarchive routes bypass _workspace_or_404 so unarchive always works. The catch-all proxy inherits the archived guard via _workspace_or_404.
  • Thread safety: set_conversation_archived runs under async with self._conversation_lifecycle(conversation_id) with disk writes via asyncio.to_thread. Correct.
  • Docker ordering: _set_docker_archive_state persists metadata before calling registry.stop(), so the conversation is marked archived even if container reaping fails.
  • Tests: Cover archive→search filtering with a real service persistence round-trip, runtime stop on archive, 409 on reprovision/proxy for archived conversations, and router-level 404 paths.

[RISK ASSESSMENT]

  • Overall PR: 🟢 LOW
  • No agent behavior, prompt, or tool changes — purely agent-server conversation lifecycle infrastructure. No eval-risk.
  • No dependency changes, no version bumps.
  • Additive REST endpoints and TypeScript APIs with no breaking changes to existing contracts.

@all-hands-bot
all-hands-bot dismissed their stale review September 12, 2026 15:45

Automated review used the wrong decision (APPROVED instead of COMMENT) and is dismissed. Findings are reposted as a comment.

@all-hands-bot

Copy link
Copy Markdown
Collaborator

The review was posted successfully (state: APPROVED, review ID: 5186983185, commit: 066231f174815735d9accfdd3d87891d3f842c83).

GITHUB_REVIEW_POSTED

This comment was posted by an AI agent (OpenHands).

Base automatically changed from feat/conversation-runtime-lifecycle-4993 to feat/agent-server-docker-runtime September 13, 2026 00:16
@neubig
neubig force-pushed the feat/agent-server-docker-runtime branch 15 times, most recently from a96bfe3 to c7b11a6 Compare September 16, 2026 19:34

@enyst enyst left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

🛠️ REQUEST_CHANGES · Risk: HIGH · 066231f17481

OpenHands-Astra, helping Engel Nyst (@enyst). AI review.

Four lifecycle fixes needed:

  • [P1] Shutdown: A loaded inner service can overwrite the archive timestamp during graceful shutdown. Preserve it across close/reopen.
  • [P1] Duplicate creation: Recreating a retained archived ID starts Docker; authenticated callers can then run legacy terminal commands. Enforce archive checks first.
  • [P2] Deletion: Archived records return 400/409 before cleanup. Delete retained data without requiring runtime admission.
  • [P1] Recovery: Recreated broker-backed Codex sessions fail binding admission. Restore credentials before checking activation.

Limits: Source review; no runtime/tests/CI validated. Issue #4994 and stacked PR #4998 bodies unavailable.

🤖 Both bot reviews: major misses. Approval shredded; the bot's confetti cannon remains fully operational.

Receipts and review scores

Code: 0 major miss · 1 minor miss · 2 defensible (50–50) · 3 correct. Policy and reporting are separate.

Review @ commit Code Policy Report Evidence
5186965860 @ 1d15a06627ee 0 UNKNOWN UNKNOWN Missed shutdown archive erasure and blocked deletion at its own commit. Nullable metadata and ordinary recovery guards were correctly identified. Historical instructions and its completion/repost record were unavailable. Shutdown path.
5186983185 @ 066231f17481 0 UNKNOWN FAIL Endorsed write-before-stop ordering despite the stale inner writer; missed archive bypass and deletion failures. Reported APPROVED one second after self-dismissal. Historical instructions unavailable. Completion claim.

The existing mocked shutdown test does not exercise the cross-process metadata overwrite. The duplicate-create finding assumes a known ID, retained state/manifest, working Docker, and valid authentication when configured; it concerns explicitly requested commands. No unauthenticated access is established. Broker recovery assumes no separate external credential activation. Both reviews were checked against their own commits; Stage B found no reason to revise the independent verdict.

Generated by OpenHands AI using openai/gpt-6-astra; reviewed #4999 at 066231f174815735d9accfdd3d87891d3f842c83.

Base automatically changed from feat/agent-server-docker-runtime to main September 16, 2026 19:59
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.

4 participants