fix(builder): fall back to LocalFilesystemSpec when no distributed AgentStateStore is configured#1841
Conversation
…entStateStore is configured BuilderConfig unconditionally wired every agent with RemoteFilesystemSpec but fell back to InMemoryAgentStateStore when no distributed AgentStateStore bean was present. HarnessAgent rejects this combination, so the app could never start on a fresh checkout with the default H2 datasource. Now when the effective AgentStateStore is local (InMemory/JsonFile), LocalFilesystemSpec is used instead, producing a consistent local-mode topology. Distributed deployments are unaffected — RemoteFilesystemSpec is still used when a distributed AgentStateStore (Redis/MySQL/etc.) is present.
|
fix issue #1840 |
…entStateStore is configured BuilderConfig unconditionally wired every agent with RemoteFilesystemSpec but fell back to InMemoryAgentStateStore when no distributed AgentStateStore bean was present. HarnessAgent rejects this combination, so the app could never start on a fresh checkout with the default H2 datasource. Now when the effective AgentStateStore is local (InMemory/JsonFile), LocalFilesystemSpec is used instead, producing a consistent local-mode topology. Distributed deployments are unaffected — RemoteFilesystemSpec is still used when a distributed AgentStateStore (Redis/MySQL/etc.) is present.
# Conflicts: # agentscope-examples/agents/agentscope-builder/src/main/java/io/agentscope/builder/web/config/BuilderConfig.java
AgentScopeJavaBot
left a comment
There was a problem hiding this comment.
🤖 AI Review
This PR fixes a genuine out-of-the-box startup failure where BuilderConfig unconditionally wired RemoteFilesystemSpec but fell back to InMemoryAgentStateStore when no distributed store was available — a combination HarnessAgent correctly rejects. The fix is clean and well-scoped: a new isLocalStateStore() helper detects local stores and switches to LocalFilesystemSpec, producing a consistent local-mode topology. The existing test (BuilderAppContextLoadTest) remains unaffected because its Mockito proxy is typed as AgentStateStore (not InMemoryAgentStateStore), so it still takes the RemoteFilesystemSpec path. One recommended improvement: the whitelist-based instanceof check is fragile — any future local AgentStateStore implementation will silently fall into the distributed branch and reproduce the original bug.
| * {@link RemoteFilesystemSpec}. | ||
| */ | ||
| private static boolean isLocalStateStore(AgentStateStore store) { | ||
| return store instanceof InMemoryAgentStateStore || store instanceof JsonFileAgentStateStore; |
There was a problem hiding this comment.
[major] The whitelist-based detection (instanceof InMemoryAgentStateStore || instanceof JsonFileAgentStateStore) is fragile: if a new local AgentStateStore implementation is added (e.g. an embedded SQLite store), it will silently fall into the distributed branch and reproduce the exact topology-mismatch bug this PR fixes.
Consider either:
- Adding a marker interface or method on
AgentStateStoreitself, e.g.default boolean isDistributed() { return true; }, overridden tofalseby local implementations; or - Inverting to a blacklist of known distributed stores:
private static boolean isLocalStateStore(AgentStateStore store) {
return !(store instanceof MysqlAgentStateStore
|| store instanceof RedisAgentStateStore
|| store instanceof JedisAgentStateStore
|| store instanceof RedissonAgentStateStore
|| store instanceof OssAgentStateStore);
}Option 1 is more robust and pushes the classification to where it belongs (the store contract). Option 2 is a quick improvement if changing the interface is out of scope for this PR.
| .addSharedPrefix("activity/")); | ||
| if (localStore) { | ||
| b.filesystem(new LocalFilesystemSpec().isolationScope(IsolationScope.USER)); | ||
| } else { |
There was a problem hiding this comment.
[nit] In local mode, LocalFilesystemSpec defaults to LocalFsMode.ROOTED with project resolving to System.getProperty("user.dir"). For the builder context, consider explicitly setting .project(cwd) so the agent's shell pwd matches the builder's configured workspace root rather than the JVM working directory:
b.filesystem(new LocalFilesystemSpec()
.project(cwd)
.isolationScope(IsolationScope.USER));This is non-blocking — the default works, but explicit is clearer.
AgentScopeJavaBot
left a comment
There was a problem hiding this comment.
🤖 AI Review
This PR fixes a genuine out-of-the-box startup failure where BuilderConfig unconditionally wired RemoteFilesystemSpec but fell back to InMemoryAgentStateStore when no distributed store was available — a combination HarnessAgent correctly rejects. The fix is clean and well-scoped: a new isLocalStateStore() helper detects local stores and switches to LocalFilesystemSpec, producing a consistent local-mode topology. The existing test (BuilderAppContextLoadTest) remains unaffected because its Mockito proxy is typed as AgentStateStore (not InMemoryAgentStateStore), so it still takes the RemoteFilesystemSpec path. One recommended improvement: the whitelist-based instanceof check is fragile — any future local AgentStateStore implementation will silently fall into the distributed branch and reproduce the original bug.
| * {@link RemoteFilesystemSpec}. | ||
| */ | ||
| private static boolean isLocalStateStore(AgentStateStore store) { | ||
| return store instanceof InMemoryAgentStateStore || store instanceof JsonFileAgentStateStore; |
There was a problem hiding this comment.
[major] The whitelist-based detection (instanceof InMemoryAgentStateStore || instanceof JsonFileAgentStateStore) is fragile: if a new local AgentStateStore implementation is added (e.g. an embedded SQLite store), it will silently fall into the distributed branch and reproduce the exact topology-mismatch bug this PR fixes.
Consider either:
- Adding a marker interface or method on
AgentStateStoreitself, e.g.default boolean isDistributed() { return true; }, overridden tofalseby local implementations; or - Inverting to a blacklist of known distributed stores:
private static boolean isLocalStateStore(AgentStateStore store) {
return !(store instanceof MysqlAgentStateStore
|| store instanceof RedisAgentStateStore
|| store instanceof JedisAgentStateStore
|| store instanceof RedissonAgentStateStore
|| store instanceof OssAgentStateStore);
}Option 1 is more robust and pushes the classification to where it belongs (the store contract). Option 2 is a quick improvement if changing the interface is out of scope for this PR.
| .addSharedPrefix("activity/")); | ||
| if (localStore) { | ||
| b.filesystem(new LocalFilesystemSpec().isolationScope(IsolationScope.USER)); | ||
| } else { |
There was a problem hiding this comment.
[nit] In local mode, LocalFilesystemSpec defaults to LocalFsMode.ROOTED with project resolving to System.getProperty("user.dir"). For the builder context, consider explicitly setting .project(cwd) so the agent's shell pwd matches the builder's configured workspace root rather than the JVM working directory:
b.filesystem(new LocalFilesystemSpec()
.project(cwd)
.isolationScope(IsolationScope.USER));This is non-blocking — the default works, but explicit is clearer.
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
BuilderConfig unconditionally wired every agent with RemoteFilesystemSpec but fell back to InMemoryAgentStateStore when no distributed AgentStateStore bean was present. HarnessAgent rejects this combination, so the app could never start on a fresh checkout with the default H2 datasource.
Now when the effective AgentStateStore is local (InMemory/JsonFile), LocalFilesystemSpec is used instead, producing a consistent local-mode topology. Distributed deployments are unaffected — RemoteFilesystemSpec is still used when a distributed AgentStateStore (Redis/MySQL/etc.) is present.
Related Issue
Fixes #1840
AgentScope-Java Version
2.0.0-SNAPSHOTDescription
Background
BuilderConfigconfigures the filesystem spec for every agent during startup. The previous implementation unconditionally usedRemoteFilesystemSpec, and fell back toInMemoryAgentStateStorewhen no distributedAgentStateStorebean was injected. However,HarnessAgentvalidates the store-vs-filesystem topology at build time —RemoteFilesystemSpecrequires a distributed store that can be shared across pods, whileInMemoryAgentStateStoreis in-process and not shareable.Problem
On a fresh checkout with the default H2 datasource (i.e. no Redis/MySQL distributed
AgentStateStorebean injected), the harness throwsIllegalStateExceptiondue to the topology mismatch, soBuilderAppcannot start at all. This is an out-of-the-box usability bug.Changes Made
File:
agentscope-examples/agents/agentscope-builder/src/main/java/io/agentscope/builder/web/config/BuilderConfig.java(+38 / -12)io.agentscope.core.state.JsonFileAgentStateStoreandio.agentscope.harness.agent.filesystem.spec.LocalFilesystemSpec.isLocalStateStore(AgentStateStore): returnstruewhen the store isInMemoryAgentStateStoreorJsonFileAgentStateStore.configureAllAgents:LocalFilesystemSpecwithIsolationScope.USER(no shared prefix, since there is no cross-pod sharing in local mode).RemoteFilesystemSpec(baseStore)with theactivity/shared prefix.How to Test
Local mode (default H2) — verify the fix:
cd agentscope-examples/agents/agentscope-builder mvn spring-boot:runExpected: the app starts normally; logs show
Effective AgentStateStore is local (InMemoryAgentStateStore); using LocalFilesystemSpec.; noIllegalStateException. Open the builder UI, create an agent, and start a conversation to verify filesystem writes succeed.Distributed mode (Redis) — verify no regression:
Inject a distributed
AgentStateStorebean (e.g. fromagentscope-extensions-redis) and start:Expected: logs show
Effective AgentStateStore is distributed (...); using RemoteFilesystemSpec.; behavior is unchanged from before the fix;activity/still goes to the sharedBaseStore.Regression tests:
mvn -pl agentscope-examples/agents/agentscope-builder testChecklist
Please check the following items before code is ready to be reviewed.
mvn spotless:applymvn test)