diff --git a/liteflow-core/pom.xml b/liteflow-core/pom.xml
index 63fc7d6ba..c3f078993 100644
--- a/liteflow-core/pom.xml
+++ b/liteflow-core/pom.xml
@@ -72,5 +72,22 @@
com.github.ben-manes.caffeine
caffeine
+
+ org.junit.jupiter
+ junit-jupiter
+ test
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+
+ false
+
+
+
+
diff --git a/liteflow-core/src/main/java/com/yomahub/liteflow/property/agent/AgentConfig.java b/liteflow-core/src/main/java/com/yomahub/liteflow/property/agent/AgentConfig.java
new file mode 100644
index 000000000..71f7d5684
--- /dev/null
+++ b/liteflow-core/src/main/java/com/yomahub/liteflow/property/agent/AgentConfig.java
@@ -0,0 +1,35 @@
+package com.yomahub.liteflow.property.agent;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+public class AgentConfig {
+ private WorkspaceConfig workspace = new WorkspaceConfig();
+ private SessionConfig session = new SessionConfig();
+ private ShellConfig shell = new ShellConfig();
+ private DefaultsConfig defaults = new DefaultsConfig();
+ private PlatformCredential openai = new PlatformCredential();
+ private PlatformCredential anthropic = new PlatformCredential();
+ private PlatformCredential gemini = new PlatformCredential();
+ private PlatformCredential dashscope = new PlatformCredential();
+ private Map openaiCompatible = new LinkedHashMap<>();
+
+ public WorkspaceConfig getWorkspace() { return workspace; }
+ public void setWorkspace(WorkspaceConfig v) { this.workspace = v; }
+ public SessionConfig getSession() { return session; }
+ public void setSession(SessionConfig v) { this.session = v; }
+ public ShellConfig getShell() { return shell; }
+ public void setShell(ShellConfig v) { this.shell = v; }
+ public DefaultsConfig getDefaults() { return defaults; }
+ public void setDefaults(DefaultsConfig v) { this.defaults = v; }
+ public PlatformCredential getOpenai() { return openai; }
+ public void setOpenai(PlatformCredential v) { this.openai = v; }
+ public PlatformCredential getAnthropic() { return anthropic; }
+ public void setAnthropic(PlatformCredential v) { this.anthropic = v; }
+ public PlatformCredential getGemini() { return gemini; }
+ public void setGemini(PlatformCredential v) { this.gemini = v; }
+ public PlatformCredential getDashscope() { return dashscope; }
+ public void setDashscope(PlatformCredential v) { this.dashscope = v; }
+ public Map getOpenaiCompatible() { return openaiCompatible; }
+ public void setOpenaiCompatible(Map v) { this.openaiCompatible = v; }
+}
diff --git a/liteflow-core/src/main/java/com/yomahub/liteflow/property/agent/DefaultsConfig.java b/liteflow-core/src/main/java/com/yomahub/liteflow/property/agent/DefaultsConfig.java
new file mode 100644
index 000000000..7c4e4c600
--- /dev/null
+++ b/liteflow-core/src/main/java/com/yomahub/liteflow/property/agent/DefaultsConfig.java
@@ -0,0 +1,8 @@
+package com.yomahub.liteflow.property.agent;
+
+public class DefaultsConfig {
+ private int maxIterations = 15;
+
+ public int getMaxIterations() { return maxIterations; }
+ public void setMaxIterations(int v) { this.maxIterations = v; }
+}
diff --git a/liteflow-core/src/main/java/com/yomahub/liteflow/property/agent/PlatformCredential.java b/liteflow-core/src/main/java/com/yomahub/liteflow/property/agent/PlatformCredential.java
new file mode 100644
index 000000000..aa6e981b8
--- /dev/null
+++ b/liteflow-core/src/main/java/com/yomahub/liteflow/property/agent/PlatformCredential.java
@@ -0,0 +1,17 @@
+package com.yomahub.liteflow.property.agent;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+public class PlatformCredential {
+ private String apiKey;
+ private String baseUrl;
+ private Map extra = new LinkedHashMap<>();
+
+ public String getApiKey() { return apiKey; }
+ public void setApiKey(String apiKey) { this.apiKey = apiKey; }
+ public String getBaseUrl() { return baseUrl; }
+ public void setBaseUrl(String baseUrl) { this.baseUrl = baseUrl; }
+ public Map getExtra() { return extra; }
+ public void setExtra(Map extra) { this.extra = extra; }
+}
diff --git a/liteflow-core/src/main/java/com/yomahub/liteflow/property/agent/SessionConfig.java b/liteflow-core/src/main/java/com/yomahub/liteflow/property/agent/SessionConfig.java
new file mode 100644
index 000000000..78caa927c
--- /dev/null
+++ b/liteflow-core/src/main/java/com/yomahub/liteflow/property/agent/SessionConfig.java
@@ -0,0 +1,16 @@
+package com.yomahub.liteflow.property.agent;
+
+import java.time.Duration;
+
+public class SessionConfig {
+ private Duration idleTimeout = Duration.ofMinutes(30);
+ private Duration cleanupInterval = Duration.ofMinutes(1);
+ private int maxSessions = 10_000;
+
+ public Duration getIdleTimeout() { return idleTimeout; }
+ public void setIdleTimeout(Duration v) { this.idleTimeout = v; }
+ public Duration getCleanupInterval() { return cleanupInterval; }
+ public void setCleanupInterval(Duration v) { this.cleanupInterval = v; }
+ public int getMaxSessions() { return maxSessions; }
+ public void setMaxSessions(int v) { this.maxSessions = v; }
+}
diff --git a/liteflow-core/src/main/java/com/yomahub/liteflow/property/agent/ShellConfig.java b/liteflow-core/src/main/java/com/yomahub/liteflow/property/agent/ShellConfig.java
new file mode 100644
index 000000000..e9b753c9d
--- /dev/null
+++ b/liteflow-core/src/main/java/com/yomahub/liteflow/property/agent/ShellConfig.java
@@ -0,0 +1,25 @@
+package com.yomahub.liteflow.property.agent;
+
+import java.time.Duration;
+import java.util.Arrays;
+import java.util.List;
+
+public class ShellConfig {
+ private ShellMode mode = ShellMode.WHITELIST;
+ private List whitelist = Arrays.asList(
+ "ls", "cat", "grep", "find", "head", "tail", "wc", "sed", "awk", "python3", "node");
+ private List blacklist = Arrays.asList("rm", "sudo", "shutdown", "mkfs", "dd");
+ private Duration timeout = Duration.ofSeconds(30);
+ private long maxOutputBytes = 1024L * 1024;
+
+ public ShellMode getMode() { return mode; }
+ public void setMode(ShellMode v) { this.mode = v; }
+ public List getWhitelist() { return whitelist; }
+ public void setWhitelist(List v) { this.whitelist = v; }
+ public List getBlacklist() { return blacklist; }
+ public void setBlacklist(List v) { this.blacklist = v; }
+ public Duration getTimeout() { return timeout; }
+ public void setTimeout(Duration v) { this.timeout = v; }
+ public long getMaxOutputBytes() { return maxOutputBytes; }
+ public void setMaxOutputBytes(long v) { this.maxOutputBytes = v; }
+}
diff --git a/liteflow-core/src/main/java/com/yomahub/liteflow/property/agent/ShellMode.java b/liteflow-core/src/main/java/com/yomahub/liteflow/property/agent/ShellMode.java
new file mode 100644
index 000000000..7fab6e30f
--- /dev/null
+++ b/liteflow-core/src/main/java/com/yomahub/liteflow/property/agent/ShellMode.java
@@ -0,0 +1,7 @@
+package com.yomahub.liteflow.property.agent;
+
+public enum ShellMode {
+ WHITELIST,
+ BLACKLIST,
+ DISABLED
+}
diff --git a/liteflow-core/src/main/java/com/yomahub/liteflow/property/agent/WorkspaceConfig.java b/liteflow-core/src/main/java/com/yomahub/liteflow/property/agent/WorkspaceConfig.java
new file mode 100644
index 000000000..c8fac6ef1
--- /dev/null
+++ b/liteflow-core/src/main/java/com/yomahub/liteflow/property/agent/WorkspaceConfig.java
@@ -0,0 +1,23 @@
+package com.yomahub.liteflow.property.agent;
+
+public class WorkspaceConfig {
+ private String root;
+ private boolean autoCreate = true;
+ private boolean cleanupOnSessionExpire = true;
+ private boolean cleanupOnJvmShutdown = false;
+ private long maxFileBytes = 10L * 1024 * 1024;
+ private int maxListSize = 1000;
+
+ public String getRoot() { return root; }
+ public void setRoot(String root) { this.root = root; }
+ public boolean isAutoCreate() { return autoCreate; }
+ public void setAutoCreate(boolean autoCreate) { this.autoCreate = autoCreate; }
+ public boolean isCleanupOnSessionExpire() { return cleanupOnSessionExpire; }
+ public void setCleanupOnSessionExpire(boolean v) { this.cleanupOnSessionExpire = v; }
+ public boolean isCleanupOnJvmShutdown() { return cleanupOnJvmShutdown; }
+ public void setCleanupOnJvmShutdown(boolean v) { this.cleanupOnJvmShutdown = v; }
+ public long getMaxFileBytes() { return maxFileBytes; }
+ public void setMaxFileBytes(long v) { this.maxFileBytes = v; }
+ public int getMaxListSize() { return maxListSize; }
+ public void setMaxListSize(int v) { this.maxListSize = v; }
+}
diff --git a/liteflow-core/src/test/java/com/yomahub/liteflow/property/agent/AgentConfigTest.java b/liteflow-core/src/test/java/com/yomahub/liteflow/property/agent/AgentConfigTest.java
new file mode 100644
index 000000000..9429d4c7e
--- /dev/null
+++ b/liteflow-core/src/test/java/com/yomahub/liteflow/property/agent/AgentConfigTest.java
@@ -0,0 +1,50 @@
+package com.yomahub.liteflow.property.agent;
+
+import org.junit.jupiter.api.Test;
+import java.time.Duration;
+import static org.junit.jupiter.api.Assertions.*;
+
+class AgentConfigTest {
+
+ @Test
+ void defaults_are_sensible() {
+ AgentConfig c = new AgentConfig();
+ assertNotNull(c.getWorkspace());
+ assertNotNull(c.getSession());
+ assertNotNull(c.getShell());
+ assertNotNull(c.getDefaults());
+ assertNotNull(c.getOpenaiCompatible());
+ assertTrue(c.getOpenaiCompatible().isEmpty());
+
+ WorkspaceConfig w = c.getWorkspace();
+ assertNull(w.getRoot());
+ assertTrue(w.isAutoCreate());
+ assertTrue(w.isCleanupOnSessionExpire());
+ assertFalse(w.isCleanupOnJvmShutdown());
+ assertEquals(10 * 1024 * 1024, w.getMaxFileBytes());
+ assertEquals(1000, w.getMaxListSize());
+
+ SessionConfig s = c.getSession();
+ assertEquals(Duration.ofMinutes(30), s.getIdleTimeout());
+ assertEquals(Duration.ofMinutes(1), s.getCleanupInterval());
+ assertEquals(10_000, s.getMaxSessions());
+
+ ShellConfig sh = c.getShell();
+ assertEquals(ShellMode.WHITELIST, sh.getMode());
+ assertNotNull(sh.getWhitelist());
+ assertNotNull(sh.getBlacklist());
+ assertEquals(Duration.ofSeconds(30), sh.getTimeout());
+ assertEquals(1024 * 1024, sh.getMaxOutputBytes());
+
+ assertEquals(15, c.getDefaults().getMaxIterations());
+ }
+
+ @Test
+ void platform_credentials_are_independent_instances() {
+ AgentConfig c = new AgentConfig();
+ c.getOpenai().setApiKey("k1");
+ assertNull(c.getAnthropic().getApiKey());
+ assertNull(c.getGemini().getApiKey());
+ assertNull(c.getDashscope().getApiKey());
+ }
+}