Skip to content

feat(harness): persist team membership for existing sessions - #3425

Open
dailingtao wants to merge 14 commits into
agentscope-ai:mainfrom
dailingtao:fuhui/persist-team-session-membership
Open

dailingtao wants to merge 14 commits into
agentscope-ai:mainfrom
dailingtao:fuhui/persist-team-session-membership

Conversation

@dailingtao

@dailingtao dailingtao commented Oct 5, 2026 •

Copy link
Copy Markdown
Contributor

AgentScope-Java Version

2.1.0-SNAPSHOT; current head 899482ad4, including main through 035487ea1.

Description

Harness already stores Team metadata and declared Agent members, with task boards and mailboxes. Applications still lack persistent membership for specific Sessions: the existing bindSession only registers notifications in memory. They cannot recover those associations after restart or enforce unique Session membership across clients.

This PR adds an explicitly enabled Java API to register, query and safely remove Team membership for existing Sessions:

  • Two Sessions of the same Agent can belong to different Teams.
  • One precisely identified Session can belong to at most one Team within the configured relationship domain, including across Team namespaces.
  • Removing membership preserves the Agent definition, Session state and workspace.

This implements the Session-membership part of #1998 for existing Leader and BYO (application-supplied) Sessions.

Usage

Use the existing createTeam to declare the Team and members; their Sessions must already be persisted. Given that Team's address, leader and worker (MemberSession values), and their stateStore:

var membership = client.sessionMembership(
        "app-relations", Map.of("sessions", stateStore));

membership.adoptTeam("alice", address, "Research", List.of(leader)).block();
var binding = membership.bindMember("alice", address, worker).block();
var current = membership.findMembership(worker.session()).block();

membership.unbindMember(
        "alice", address, worker.session(), binding.token()).block();

adoptTeam requires exactly one initial Leader and optional BYO members. getTeam and listMemberships query the Team and current associations. Binding is restricted to its declared members.

Reactive callers compose the returned Mono operations; this fragment uses .block() at an application entry point. See the full setup and contracts (also documented in Chinese).

Storage and consistency

  • Reuse existing Team metadata and declared members. A metadata marker adds a logical Team ID/display name, separate from the namespace/teamName address; the new relationship record stores only associations and adoption receipts.
  • Atomic version checks (CAS) on one record per relationship domain/owner enforce uniqueness across clients. Membership reuses BaseStore.supportsAtomicSessionStorage() and putIfVersion, the same capability used by native StoreSessionStorage. In-memory and JDBC stores already declare support; JDBC provides restart persistence. The default capability remains false for legacy/custom providers. Record size/contention grows with the owner's associations.
  • Adoption spans two records. Failure between writes leaves observable PENDING; retrying the same complete request can resume it. Committed replays do not restore members removed later.
  • Identical binds retain their token. Unbinding requires the expected binding token, so a delayed removal cannot erase a newer binding.

CAS contention has bounded retries; storage errors propagate. The common JDBC, independent MySQL and PostgreSQL providers recognize specific duplicate-key signals and preserve other SQL failure causes. Private schema-1 records accept historical BYO data and reject unknown formats without resetting data; long storage addresses use bounded encodings.

Compatibility and scope

Existing TeamClient implementations, public records/JSON and static teamsMode remain available. No additional storage SPI is introduced: custom stores opt into the existing BaseStore capability and honor its atomicity/error contract. Adopted Teams' listMembers projects Session IDs from the same relationships; unbound declarations have an empty ID. PENDING preserves the original directory. completeTeam preserves membership; enabled clients and clients observing adopted Teams use strict CAS.

Before adoption, stop/drain older or unenabled writers, upgrade all writers, and enable each client. Adopted Teams do not support mixed-version writes. Rollback requires a compatible reader or restoring a pre-adoption backup with writers stopped.

This trusted programming API uses explicit AgentStateStore mappings and SessionKey(stateStoreDomain, owner, sessionId). Session and Team owners must match. Applications provide consistent store-domain IDs and definition authorization. Native 2.1 AgentSession/SessionLog discovery and identity mapping are not automatic.

The unmerged draft VersionedBaseStore has been removed. Clients using that draft type must switch to BaseStore, explicitly declare the capability and recompile; persisted membership data needs no migration.

This change does not add model tools, Worker creation, scheduling or Service Team/Run integration. Unbinding does not stop runs, revoke permissions or unregister notifications.

Closes #3424

Related to #1998; the broader App Teams request remains open.

Related to #3422; its notification-isolation patch is separate and is not included here.

Validation

Implementation validation at 863cccafd (Java 17, macOS). Current head 899482ad4 only restores existing comment wording; comparison with comments removed confirms identical Java source. Spotless and diff checks passed for that follow-up; the full suite was not rerun for comment-only edits:

  • Full mvn -B -T1 clean verify: all 97 reactor modules passed, with 10,809 reported tests, zero failures/errors and 225 conditional or previously disabled skips (10,584 executed and passed).
  • Focused regression: 199 cases passed, zero failures/errors/skips. Covers Session membership, legacy Team behavior, independent-client contention, persistent reopen, failure recovery/replay, stale tokens, historical JSON, JDBC/Skill behavior, and the newly admitted SQL providers' error handling.
  • Added behavior checks for a plain BaseStore declaring the existing capability, legacy providers retaining default false, and reuse by native StoreSessionStorage. These checks exposed the old interface restriction before the change. SQL regressions likewise failed before correcting the constraint classification.
  • Focused command: mvn -B -T1 -pl agentscope-extensions/agentscope-extensions-jdbc,agentscope-extensions/agentscope-extensions-mysql,agentscope-extensions/agentscope-extensions-postgresql -am spotless:apply test -Dtest=TeamSessionMembershipTest,LocalTeamClientTest,TeamSessionMembershipH2Test,JdbcStoreH2Test,JdbcStoreConstraintTest,JdbcAgentSkillRepositoryH2Test,JdbcAgentSkillRepositorySqliteTest,PostgresBaseStoreTest.
  • Docs: npm test (13 passed), npm run validate, and npm run broken-links passed. Final Spotless and diff checks passed.
  • H2/SQLite tests use real databases; other vendors' error diagnostics are simulated. MySQL/PostgreSQL were not integration-tested locally. Conditional external-service/Docker skips remain explicit in the full result.
  • Current-head documentation CI, Windows build, license and module-sync checks passed. Linux CI failed in the unchanged DashScopeNonStreamingBlockingBehaviorTest.testDashScopeChatModelNonBlocking at line 86 (expected: not <null>); Harness and the changed storage modules were skipped after that failure.
  • The failing test is identical in the CI merge and its main parent. An unchanged local rerun passed (1 test, no failures/errors/skips). The test waits three seconds and discards asynchronous errors, so the log does not establish whether the failure was a timeout or an API-call error. The upstream job rerun returned HTTP 403 for the contributor account; a maintainer retry is needed. No assertions or production code were changed to bypass this failure.

Checklist

  • Code has been formatted with mvn spotless:apply
  • Current local focused/full verification and documentation checks passed; remote CI is tracked above
  • Javadoc comments follow project conventions
  • EN/ZH documentation is updated
  • Code is ready for review

@codecov

codecov Bot commented Oct 6, 2026 •

Copy link
Copy Markdown

A JdbcStore ObjectMapper configured with NON_NULL legitimately omits null
owner fields, but the strict decoder rejected such committed relationship
records, making them unreadable. Nullable fields now decode as null when
absent while required fields and schema checks still reject damaged data.
All associations are application-supplied BYO and no behavior branches on
the enum, so remove it and its persisted field before first release.
Previously written records remain readable; the unknown field is ignored.

@guslegend0510 guslegend0510 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.

Thanks for the work! I ran the related tests locally and they pass. Two problems before LGTM:

1. A failed adoption can break a Team for good

If I adopt Team B with a Session that is already in Team A, adoption fails. But Team B still gets marked as "PENDING", and after that:

  • listMembers and broadcastMessage on Team B throw errors
  • I can't retry with a different Session either ("different adoption request")

So one wrong call breaks Team B until Team A lets go of that Session.

Please check for this conflict before writing the marker. Also, while a Team is PENDING, listMembers could just return the old list instead of throwing.

2. Duplicate-key check in JdbcStore misses some databases

The new check only knows PostgreSQL/H2, MySQL 1062 and SQLite. Oracle, SQL Server and MySQL 1022 duplicates now throw an exception instead of returning false. That can break existing code like RemoteFilesystem.write for people who use those databases through a custom dialect.

JdbcAgentSkillRepository.isUniqueViolation in the same module already handles all of these. Could you reuse it?

Keep the legacy member directory and broadcasts available while adoption is pending. Share precise JDBC duplicate-key classification with Skill to preserve existing-file failures across supported diagnostics.
@dailingtao

Copy link
Copy Markdown
Contributor Author

@guslegend0510 Thanks for reproducing these and calling them out. Both are addressed in a24076e0.

  1. Adoption now checks every initial Session against the existing owner associations before installing the Team marker. An already occupied Leader or BYO member rejects the whole request without changing Team metadata, existing associations or tokens; the caller can retry with a different valid Session. While a genuine interrupted adoption is PENDING, legacy listMembers and broadcastMessage continue using the original declared directory, including its original Session fields. getTeam still reports PENDING with no committed associations and listMemberships still rejects it. A claim that races after preflight can still leave PENDING: the owner CAS preserves unique membership, and the identical adoption request can resume after the conflict is released. Tests cover that interleaving and file-backed H2 reopen/retry.

  2. Store and Skill now share JdbcConstraintErrors.isDuplicateKey, extracting the existing repository's diagnostic rules rather than making Store depend on the Skill repository. It includes MySQL 1022/1062, Oracle 1, SQL Server 2601/2627, SQLState 23505 and the existing Informix codes. SQLite matches only explicit PRIMARYKEY/UNIQUE results; unknown constraints remain storage errors. CHECK, foreign-key, NOT NULL and connection failures retain their original SQL cause. The added diagnostic tests also exercise RemoteFilesystem.write and verify it returns the existing-file failure result rather than throwing on duplicates.

Validation: 118 targeted tests passed with no failures/errors/skips; all 96 reactor modules passed mvn -B -T1 clean verify (11:15). EN/ZH docs, 12 documentation tests, strict Mintlify validation and broken-link checks passed. H2/SQLite constraints use real databases; the other vendors' diagnostics are simulated, not live integration tests.

@guslegend0510 guslegend0510 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.

LGTM

@dailingtao

Copy link
Copy Markdown
Contributor Author

@chickenlj jun哥 这个PR 有时间也可以看下, 是对齐 py 那边 agent team 相关的 PR

This branch has not been deployed

No deployments
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.

[Feature]: Persist Team membership for existing Harness Sessions

3 participants