state) {
+ this.state = state == null ? new LinkedHashMap<>() : state;
+ }
+
+ /**
+ * 获取创建时间。
+ *
+ * @return 创建时间
+ */
+ public Instant getCreatedAt() {
+ return createdAt;
+ }
+
+ /**
+ * 设置创建时间。
+ *
+ * @param createdAt 创建时间
+ */
+ public void setCreatedAt(Instant createdAt) {
+ this.createdAt = createdAt == null ? Instant.now() : createdAt;
+ }
+}
diff --git a/easy-agents-agent-runtime/src/main/java/com/easyagents/agent/runtime/persistence/AgentRuntimeState.java b/easy-agents-agent-runtime/src/main/java/com/easyagents/agent/runtime/persistence/AgentRuntimeState.java
new file mode 100644
index 0000000..bea03c0
--- /dev/null
+++ b/easy-agents-agent-runtime/src/main/java/com/easyagents/agent/runtime/persistence/AgentRuntimeState.java
@@ -0,0 +1,86 @@
+package com.easyagents.agent.runtime.persistence;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * 运行时状态项。
+ *
+ * MVP 中 value 是 JVM 对象态,不是稳定的传输格式,
+ * 非内存 {@link AgentSessionStore} 实现必须显式完成序列化,
+ * 再写入外部存储。
+ */
+public class AgentRuntimeState {
+
+ private String name;
+ private Object value;
+ private Map metadata = new LinkedHashMap<>();
+
+ /**
+ * 创建a 状态项。
+ *
+ * @param name 状态名称
+ * @param value 状态值
+ * @return 状态项
+ */
+ public static AgentRuntimeState of(String name, Object value) {
+ AgentRuntimeState state = new AgentRuntimeState();
+ state.setName(name);
+ state.setValue(value);
+ return state;
+ }
+
+ /**
+ * 获取状态名称。
+ *
+ * @return 状态名称
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * 设置状态名称。
+ *
+ * @param name 状态名称
+ */
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ /**
+ * 获取状态值。
+ *
+ * @return 状态值
+ */
+ public Object getValue() {
+ return value;
+ }
+
+ /**
+ * 设置状态值。
+ *
+ * @param value 状态值
+ */
+ public void setValue(Object value) {
+ this.value = value;
+ }
+
+ /**
+ * 获取元数据。
+ *
+ * @return 元数据
+ */
+ public Map getMetadata() {
+ return metadata;
+ }
+
+ /**
+ * 设置元数据。
+ *
+ * @param metadata 元数据
+ */
+ public void setMetadata(Map metadata) {
+ this.metadata = metadata == null ? new LinkedHashMap<>() : metadata;
+ }
+}
diff --git a/easy-agents-agent-runtime/src/main/java/com/easyagents/agent/runtime/persistence/AgentSessionStore.java b/easy-agents-agent-runtime/src/main/java/com/easyagents/agent/runtime/persistence/AgentSessionStore.java
new file mode 100644
index 0000000..f2a8d2a
--- /dev/null
+++ b/easy-agents-agent-runtime/src/main/java/com/easyagents/agent/runtime/persistence/AgentSessionStore.java
@@ -0,0 +1,70 @@
+package com.easyagents.agent.runtime.persistence;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+
+/**
+ * 用于 AgentScope 类运行时会话状态的存储 SPI。
+ *
+ */
+public interface AgentSessionStore {
+
+ /**
+ * 保存one state value。
+ *
+ * @param sessionKey 会话键
+ * @param name 状态名称
+ * @param state 状态值
+ */
+ void save(String sessionKey, String name, AgentRuntimeState state);
+
+ /**
+ * 保存a state list。
+ *
+ * @param sessionKey 会话键
+ * @param name 状态名称
+ * @param states 状态列表
+ */
+ void saveList(String sessionKey, String name, List states);
+
+ /**
+ * 获取one state value。
+ *
+ * @param sessionKey 会话键
+ * @param name 状态名称
+ * @return 可选状态
+ */
+ Optional get(String sessionKey, String name);
+
+ /**
+ * 获取a state list。
+ *
+ * @param sessionKey 会话键
+ * @param name 状态名称
+ * @return 状态列表
+ */
+ List getList(String sessionKey, String name);
+
+ /**
+ * 返回是否session key exists。
+ *
+ * @param sessionKey 会话键
+ * @return 存在时为 true
+ */
+ boolean exists(String sessionKey);
+
+ /**
+ * 删除a session key。
+ *
+ * @param sessionKey 会话键
+ */
+ void delete(String sessionKey);
+
+ /**
+ * 列出会话键列表。
+ *
+ * @return 会话键列表
+ */
+ Set listSessionKeys();
+}
diff --git a/easy-agents-agent-runtime/src/main/java/com/easyagents/agent/runtime/persistence/AgentSessionStoreException.java b/easy-agents-agent-runtime/src/main/java/com/easyagents/agent/runtime/persistence/AgentSessionStoreException.java
new file mode 100644
index 0000000..c743c8f
--- /dev/null
+++ b/easy-agents-agent-runtime/src/main/java/com/easyagents/agent/runtime/persistence/AgentSessionStoreException.java
@@ -0,0 +1,28 @@
+package com.easyagents.agent.runtime.persistence;
+
+import com.easyagents.agent.runtime.AgentRuntimeException;
+
+/**
+ * 会话持久化异常。
+ */
+public class AgentSessionStoreException extends AgentRuntimeException {
+
+ /**
+ * 创建会话持久化异常。
+ *
+ * @param message 错误消息
+ */
+ public AgentSessionStoreException(String message) {
+ super(message);
+ }
+
+ /**
+ * 创建会话持久化异常。
+ *
+ * @param message 错误消息
+ * @param cause 原始异常
+ */
+ public AgentSessionStoreException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/easy-agents-agent-runtime/src/main/java/com/easyagents/agent/runtime/persistence/json/AgentSessionStateCodec.java b/easy-agents-agent-runtime/src/main/java/com/easyagents/agent/runtime/persistence/json/AgentSessionStateCodec.java
new file mode 100644
index 0000000..c9ff425
--- /dev/null
+++ b/easy-agents-agent-runtime/src/main/java/com/easyagents/agent/runtime/persistence/json/AgentSessionStateCodec.java
@@ -0,0 +1,62 @@
+package com.easyagents.agent.runtime.persistence.json;
+
+import com.easyagents.agent.runtime.persistence.AgentRuntimeState;
+import com.easyagents.agent.runtime.persistence.AgentSessionStoreException;
+import io.agentscope.core.state.State;
+import io.agentscope.core.util.JsonUtils;
+
+/**
+ * AgentScope 状态的 JSON 编解码器。
+ */
+public class AgentSessionStateCodec {
+
+ /**
+ * 将状态对象编码为可持久化记录。
+ *
+ * @param state 状态对象
+ * @return 可持久化记录
+ */
+ public SerializedAgentRuntimeState encode(AgentRuntimeState state) {
+ if (state == null || state.getValue() == null) {
+ return null;
+ }
+ Object value = state.getValue();
+ if (!(value instanceof State)) {
+ throw new AgentSessionStoreException("Only AgentScope State values can be serialized: "
+ + value.getClass().getName());
+ }
+ SerializedAgentRuntimeState serialized = new SerializedAgentRuntimeState();
+ serialized.setName(state.getName());
+ serialized.setStateClassName(value.getClass().getName());
+ serialized.setStateJson(JsonUtils.getJsonCodec().toJson(value));
+ serialized.setMetadata(state.getMetadata());
+ return serialized;
+ }
+
+ /**
+ * 将持久化记录解码为运行时状态。
+ *
+ * @param serialized 可持久化记录
+ * @return 运行时状态
+ */
+ @SuppressWarnings("unchecked")
+ public AgentRuntimeState decode(SerializedAgentRuntimeState serialized) {
+ if (serialized == null) {
+ return null;
+ }
+ try {
+ Class> clazz = Class.forName(serialized.getStateClassName());
+ if (!State.class.isAssignableFrom(clazz)) {
+ throw new AgentSessionStoreException("Serialized state class is not AgentScope State: "
+ + serialized.getStateClassName());
+ }
+ State value = JsonUtils.getJsonCodec().fromJson(serialized.getStateJson(), (Class extends State>) clazz);
+ AgentRuntimeState state = AgentRuntimeState.of(serialized.getName(), value);
+ state.setMetadata(serialized.getMetadata());
+ return state;
+ } catch (ClassNotFoundException e) {
+ throw new AgentSessionStoreException("Serialized state class not found: "
+ + serialized.getStateClassName(), e);
+ }
+ }
+}
diff --git a/easy-agents-agent-runtime/src/main/java/com/easyagents/agent/runtime/persistence/json/AgentSessionStoreBackend.java b/easy-agents-agent-runtime/src/main/java/com/easyagents/agent/runtime/persistence/json/AgentSessionStoreBackend.java
new file mode 100644
index 0000000..0cb27dd
--- /dev/null
+++ b/easy-agents-agent-runtime/src/main/java/com/easyagents/agent/runtime/persistence/json/AgentSessionStoreBackend.java
@@ -0,0 +1,69 @@
+package com.easyagents.agent.runtime.persistence.json;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+
+/**
+ * JSON 会话状态的底层键值存储后端。
+ */
+public interface AgentSessionStoreBackend {
+
+ /**
+ * 保存单个状态记录。
+ *
+ * @param sessionKey 会话键
+ * @param name 状态名称
+ * @param state 状态记录
+ */
+ void save(String sessionKey, String name, SerializedAgentRuntimeState state);
+
+ /**
+ * 保存状态记录列表。
+ *
+ * @param sessionKey 会话键
+ * @param name 状态名称
+ * @param states 状态记录列表
+ */
+ void saveList(String sessionKey, String name, List states);
+
+ /**
+ * 获取单个状态记录。
+ *
+ * @param sessionKey 会话键
+ * @param name 状态名称
+ * @return 状态记录
+ */
+ Optional get(String sessionKey, String name);
+
+ /**
+ * 获取状态记录列表。
+ *
+ * @param sessionKey 会话键
+ * @param name 状态名称
+ * @return 状态记录列表
+ */
+ List getList(String sessionKey, String name);
+
+ /**
+ * 判断会话是否存在。
+ *
+ * @param sessionKey 会话键
+ * @return 存在时为 true
+ */
+ boolean exists(String sessionKey);
+
+ /**
+ * 删除会话。
+ *
+ * @param sessionKey 会话键
+ */
+ void delete(String sessionKey);
+
+ /**
+ * 列出全部会话键。
+ *
+ * @return 会话键集合
+ */
+ Set listSessionKeys();
+}
diff --git a/easy-agents-agent-runtime/src/main/java/com/easyagents/agent/runtime/persistence/json/FileAgentSessionStoreBackend.java b/easy-agents-agent-runtime/src/main/java/com/easyagents/agent/runtime/persistence/json/FileAgentSessionStoreBackend.java
new file mode 100644
index 0000000..938c6b0
--- /dev/null
+++ b/easy-agents-agent-runtime/src/main/java/com/easyagents/agent/runtime/persistence/json/FileAgentSessionStoreBackend.java
@@ -0,0 +1,172 @@
+package com.easyagents.agent.runtime.persistence.json;
+
+import com.easyagents.agent.runtime.persistence.AgentSessionStoreException;
+import io.agentscope.core.util.JsonUtils;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import java.util.*;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * 基于本地文件的 JSON 会话状态后端。
+ */
+public class FileAgentSessionStoreBackend implements AgentSessionStoreBackend {
+
+ private static final Pattern SAFE_NAME = Pattern.compile("[A-Za-z0-9._-]+");
+ private final Path rootDirectory;
+
+ /**
+ * 创建文件后端。
+ *
+ * @param rootDirectory 根目录
+ */
+ public FileAgentSessionStoreBackend(Path rootDirectory) {
+ if (rootDirectory == null) {
+ throw new AgentSessionStoreException("Agent session root directory is required.");
+ }
+ this.rootDirectory = rootDirectory;
+ try {
+ Files.createDirectories(rootDirectory);
+ } catch (IOException e) {
+ throw new AgentSessionStoreException("Failed to create agent session root directory: " + rootDirectory, e);
+ }
+ }
+
+ @Override
+ public void save(String sessionKey, String name, SerializedAgentRuntimeState state) {
+ if (state == null) {
+ return;
+ }
+ Path path = statePath(sessionKey, name);
+ createParent(path);
+ try {
+ Files.writeString(path, JsonUtils.getJsonCodec().toPrettyJson(state), StandardCharsets.UTF_8,
+ StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.WRITE);
+ } catch (IOException e) {
+ throw new AgentSessionStoreException("Failed to save agent session state: " + sessionKey + "/" + name, e);
+ }
+ }
+
+ @Override
+ public void saveList(String sessionKey, String name, List states) {
+ Path path = listPath(sessionKey, name);
+ createParent(path);
+ try (BufferedWriter writer = Files.newBufferedWriter(path, StandardCharsets.UTF_8,
+ StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.WRITE)) {
+ if (states == null) {
+ return;
+ }
+ for (SerializedAgentRuntimeState state : states) {
+ writer.write(JsonUtils.getJsonCodec().toJson(state));
+ writer.newLine();
+ }
+ } catch (IOException e) {
+ throw new AgentSessionStoreException("Failed to save agent session state list: " + sessionKey + "/" + name, e);
+ }
+ }
+
+ @Override
+ public Optional get(String sessionKey, String name) {
+ Path path = statePath(sessionKey, name);
+ if (!Files.exists(path)) {
+ return Optional.empty();
+ }
+ try {
+ String json = Files.readString(path, StandardCharsets.UTF_8);
+ return Optional.of(JsonUtils.getJsonCodec().fromJson(json, SerializedAgentRuntimeState.class));
+ } catch (IOException e) {
+ throw new AgentSessionStoreException("Failed to load agent session state: " + sessionKey + "/" + name, e);
+ }
+ }
+
+ @Override
+ public List getList(String sessionKey, String name) {
+ Path path = listPath(sessionKey, name);
+ if (!Files.exists(path)) {
+ return List.of();
+ }
+ List states = new ArrayList<>();
+ try (BufferedReader reader = Files.newBufferedReader(path, StandardCharsets.UTF_8)) {
+ String line;
+ while ((line = reader.readLine()) != null) {
+ if (!line.isBlank()) {
+ states.add(JsonUtils.getJsonCodec().fromJson(line, SerializedAgentRuntimeState.class));
+ }
+ }
+ return states;
+ } catch (IOException e) {
+ throw new AgentSessionStoreException("Failed to load agent session state list: " + sessionKey + "/" + name, e);
+ }
+ }
+
+ @Override
+ public boolean exists(String sessionKey) {
+ return Files.isDirectory(sessionDirectory(sessionKey));
+ }
+
+ @Override
+ public void delete(String sessionKey) {
+ Path directory = sessionDirectory(sessionKey);
+ if (!Files.exists(directory)) {
+ return;
+ }
+ try (Stream paths = Files.walk(directory)) {
+ List ordered = paths.sorted(Comparator.reverseOrder()).collect(Collectors.toList());
+ for (Path path : ordered) {
+ Files.deleteIfExists(path);
+ }
+ } catch (IOException e) {
+ throw new AgentSessionStoreException("Failed to delete agent session: " + sessionKey, e);
+ }
+ }
+
+ @Override
+ public Set listSessionKeys() {
+ if (!Files.exists(rootDirectory)) {
+ return Set.of();
+ }
+ try (Stream paths = Files.list(rootDirectory)) {
+ return paths.filter(Files::isDirectory)
+ .map(path -> path.getFileName().toString())
+ .collect(Collectors.toCollection(LinkedHashSet::new));
+ } catch (IOException e) {
+ throw new AgentSessionStoreException("Failed to list agent sessions.", e);
+ }
+ }
+
+ private Path statePath(String sessionKey, String name) {
+ return sessionDirectory(sessionKey).resolve(safeName(name) + ".json");
+ }
+
+ private Path listPath(String sessionKey, String name) {
+ return sessionDirectory(sessionKey).resolve(safeName(name) + ".jsonl");
+ }
+
+ private Path sessionDirectory(String sessionKey) {
+ return rootDirectory.resolve(safeName(sessionKey));
+ }
+
+ private String safeName(String value) {
+ if (value == null || value.isBlank() || !SAFE_NAME.matcher(value).matches()
+ || value.contains("..")) {
+ throw new AgentSessionStoreException("Unsafe agent session storage key: " + value);
+ }
+ return value;
+ }
+
+ private void createParent(Path path) {
+ try {
+ Files.createDirectories(path.getParent());
+ } catch (IOException e) {
+ throw new AgentSessionStoreException("Failed to create agent session directory: " + path.getParent(), e);
+ }
+ }
+}
diff --git a/easy-agents-agent-runtime/src/main/java/com/easyagents/agent/runtime/persistence/json/JsonAgentSessionStore.java b/easy-agents-agent-runtime/src/main/java/com/easyagents/agent/runtime/persistence/json/JsonAgentSessionStore.java
new file mode 100644
index 0000000..fc13d20
--- /dev/null
+++ b/easy-agents-agent-runtime/src/main/java/com/easyagents/agent/runtime/persistence/json/JsonAgentSessionStore.java
@@ -0,0 +1,106 @@
+package com.easyagents.agent.runtime.persistence.json;
+
+import com.easyagents.agent.runtime.persistence.AgentRuntimeState;
+import com.easyagents.agent.runtime.persistence.AgentSessionStore;
+import com.easyagents.agent.runtime.persistence.AgentSessionStoreException;
+
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+
+/**
+ * 基于 JSON 记录的默认非内存会话存储。
+ */
+public class JsonAgentSessionStore implements AgentSessionStore {
+
+ private final AgentSessionStoreBackend backend;
+ private final AgentSessionStateCodec codec;
+
+ /**
+ * 创建文件型 JSON 会话存储。
+ *
+ * @param rootDirectory 根目录
+ */
+ public JsonAgentSessionStore(Path rootDirectory) {
+ this(new FileAgentSessionStoreBackend(rootDirectory), new AgentSessionStateCodec());
+ }
+
+ /**
+ * 创建可替换后端的 JSON 会话存储。
+ *
+ * @param backend 底层存储后端
+ */
+ public JsonAgentSessionStore(AgentSessionStoreBackend backend) {
+ this(backend, new AgentSessionStateCodec());
+ }
+
+ /**
+ * 创建可替换后端和编解码器的 JSON 会话存储。
+ *
+ * @param backend 底层存储后端
+ * @param codec 状态编解码器
+ */
+ public JsonAgentSessionStore(AgentSessionStoreBackend backend, AgentSessionStateCodec codec) {
+ if (backend == null) {
+ throw new AgentSessionStoreException("Agent session store backend is required.");
+ }
+ this.backend = backend;
+ this.codec = codec == null ? new AgentSessionStateCodec() : codec;
+ }
+
+ @Override
+ public void save(String sessionKey, String name, AgentRuntimeState state) {
+ SerializedAgentRuntimeState serialized = codec.encode(state);
+ if (serialized != null) {
+ backend.save(sessionKey, name, serialized);
+ }
+ }
+
+ @Override
+ public void saveList(String sessionKey, String name, List states) {
+ List serialized = new ArrayList<>();
+ if (states != null) {
+ for (AgentRuntimeState state : states) {
+ SerializedAgentRuntimeState item = codec.encode(state);
+ if (item != null) {
+ serialized.add(item);
+ }
+ }
+ }
+ backend.saveList(sessionKey, name, serialized);
+ }
+
+ @Override
+ public Optional get(String sessionKey, String name) {
+ return backend.get(sessionKey, name).map(codec::decode);
+ }
+
+ @Override
+ public List getList(String sessionKey, String name) {
+ List states = new ArrayList<>();
+ for (SerializedAgentRuntimeState item : backend.getList(sessionKey, name)) {
+ AgentRuntimeState state = codec.decode(item);
+ if (state != null) {
+ states.add(state);
+ }
+ }
+ return states;
+ }
+
+ @Override
+ public boolean exists(String sessionKey) {
+ return backend.exists(sessionKey);
+ }
+
+ @Override
+ public void delete(String sessionKey) {
+ backend.delete(sessionKey);
+ }
+
+ @Override
+ public Set listSessionKeys() {
+ return backend.listSessionKeys();
+ }
+}
diff --git a/easy-agents-agent-runtime/src/main/java/com/easyagents/agent/runtime/persistence/json/SerializedAgentRuntimeState.java b/easy-agents-agent-runtime/src/main/java/com/easyagents/agent/runtime/persistence/json/SerializedAgentRuntimeState.java
new file mode 100644
index 0000000..2cb01fc
--- /dev/null
+++ b/easy-agents-agent-runtime/src/main/java/com/easyagents/agent/runtime/persistence/json/SerializedAgentRuntimeState.java
@@ -0,0 +1,107 @@
+package com.easyagents.agent.runtime.persistence.json;
+
+import java.time.Instant;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * 可跨进程持久化的 AgentScope 状态记录。
+ */
+public class SerializedAgentRuntimeState {
+
+ private String name;
+ private String stateClassName;
+ private String stateJson;
+ private Map metadata = new LinkedHashMap<>();
+ private Instant createdAt = Instant.now();
+
+ /**
+ * 获取状态名称。
+ *
+ * @return 状态名称
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * 设置状态名称。
+ *
+ * @param name 状态名称
+ */
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ /**
+ * 获取状态类名。
+ *
+ * @return 状态类名
+ */
+ public String getStateClassName() {
+ return stateClassName;
+ }
+
+ /**
+ * 设置状态类名。
+ *
+ * @param stateClassName 状态类名
+ */
+ public void setStateClassName(String stateClassName) {
+ this.stateClassName = stateClassName;
+ }
+
+ /**
+ * 获取状态 JSON。
+ *
+ * @return 状态 JSON
+ */
+ public String getStateJson() {
+ return stateJson;
+ }
+
+ /**
+ * 设置状态 JSON。
+ *
+ * @param stateJson 状态 JSON
+ */
+ public void setStateJson(String stateJson) {
+ this.stateJson = stateJson;
+ }
+
+ /**
+ * 获取元数据。
+ *
+ * @return 元数据
+ */
+ public Map getMetadata() {
+ return metadata;
+ }
+
+ /**
+ * 设置元数据。
+ *
+ * @param metadata 元数据
+ */
+ public void setMetadata(Map metadata) {
+ this.metadata = metadata == null ? new LinkedHashMap<>() : metadata;
+ }
+
+ /**
+ * 获取创建时间。
+ *
+ * @return 创建时间
+ */
+ public Instant getCreatedAt() {
+ return createdAt;
+ }
+
+ /**
+ * 设置创建时间。
+ *
+ * @param createdAt 创建时间
+ */
+ public void setCreatedAt(Instant createdAt) {
+ this.createdAt = createdAt == null ? Instant.now() : createdAt;
+ }
+}
diff --git a/easy-agents-agent-runtime/src/main/java/com/easyagents/agent/runtime/persistence/memory/InMemoryAgentSessionStore.java b/easy-agents-agent-runtime/src/main/java/com/easyagents/agent/runtime/persistence/memory/InMemoryAgentSessionStore.java
new file mode 100644
index 0000000..9470f89
--- /dev/null
+++ b/easy-agents-agent-runtime/src/main/java/com/easyagents/agent/runtime/persistence/memory/InMemoryAgentSessionStore.java
@@ -0,0 +1,59 @@
+package com.easyagents.agent.runtime.persistence.memory;
+
+import com.easyagents.agent.runtime.persistence.AgentRuntimeState;
+import com.easyagents.agent.runtime.persistence.AgentSessionStore;
+
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * 用于测试和单节点 MVP 的内存会话存储。
+ */
+public class InMemoryAgentSessionStore implements AgentSessionStore {
+
+ private final Map>> states = new ConcurrentHashMap<>();
+
+ @Override
+ public void save(String sessionKey, String name, AgentRuntimeState state) {
+ if (sessionKey == null || name == null || state == null) {
+ return;
+ }
+ saveList(sessionKey, name, List.of(state));
+ }
+
+ @Override
+ public void saveList(String sessionKey, String name, List states) {
+ if (sessionKey == null || name == null) {
+ return;
+ }
+ this.states.computeIfAbsent(sessionKey, key -> new ConcurrentHashMap<>())
+ .put(name, states == null ? new ArrayList<>() : new ArrayList<>(states));
+ }
+
+ @Override
+ public Optional get(String sessionKey, String name) {
+ List list = getList(sessionKey, name);
+ return list.isEmpty() ? Optional.empty() : Optional.ofNullable(list.get(0));
+ }
+
+ @Override
+ public List getList(String sessionKey, String name) {
+ Map> byName = states.getOrDefault(sessionKey, new LinkedHashMap<>());
+ return new ArrayList<>(byName.getOrDefault(name, new ArrayList<>()));
+ }
+
+ @Override
+ public boolean exists(String sessionKey) {
+ return states.containsKey(sessionKey);
+ }
+
+ @Override
+ public void delete(String sessionKey) {
+ states.remove(sessionKey);
+ }
+
+ @Override
+ public Set listSessionKeys() {
+ return new LinkedHashSet<>(states.keySet());
+ }
+}
diff --git a/easy-agents-agent-runtime/src/main/java/com/easyagents/agent/runtime/persistence/noop/NoopAgentConversationRecorder.java b/easy-agents-agent-runtime/src/main/java/com/easyagents/agent/runtime/persistence/noop/NoopAgentConversationRecorder.java
new file mode 100644
index 0000000..433aba0
--- /dev/null
+++ b/easy-agents-agent-runtime/src/main/java/com/easyagents/agent/runtime/persistence/noop/NoopAgentConversationRecorder.java
@@ -0,0 +1,16 @@
+package com.easyagents.agent.runtime.persistence.noop;
+
+import com.easyagents.agent.runtime.AgentRunRequest;
+import com.easyagents.agent.runtime.event.AgentRuntimeEvent;
+import com.easyagents.agent.runtime.persistence.AgentConversationRecorder;
+
+/**
+ * 空操作会话记录器。
+ */
+public enum NoopAgentConversationRecorder implements AgentConversationRecorder {
+ INSTANCE;
+
+ @Override
+ public void record(AgentRunRequest request, AgentRuntimeEvent event) {
+ }
+}
diff --git a/easy-agents-agent-runtime/src/main/java/com/easyagents/agent/runtime/persistence/noop/NoopAgentSessionStore.java b/easy-agents-agent-runtime/src/main/java/com/easyagents/agent/runtime/persistence/noop/NoopAgentSessionStore.java
new file mode 100644
index 0000000..36240e8
--- /dev/null
+++ b/easy-agents-agent-runtime/src/main/java/com/easyagents/agent/runtime/persistence/noop/NoopAgentSessionStore.java
@@ -0,0 +1,48 @@
+package com.easyagents.agent.runtime.persistence.noop;
+
+import com.easyagents.agent.runtime.persistence.AgentRuntimeState;
+import com.easyagents.agent.runtime.persistence.AgentSessionStore;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+
+/**
+ * 空操作会话存储。
+ */
+public enum NoopAgentSessionStore implements AgentSessionStore {
+ INSTANCE;
+
+ @Override
+ public void save(String sessionKey, String name, AgentRuntimeState state) {
+ }
+
+ @Override
+ public void saveList(String sessionKey, String name, List states) {
+ }
+
+ @Override
+ public Optional get(String sessionKey, String name) {
+ return Optional.empty();
+ }
+
+ @Override
+ public List getList(String sessionKey, String name) {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public boolean exists(String sessionKey) {
+ return false;
+ }
+
+ @Override
+ public void delete(String sessionKey) {
+ }
+
+ @Override
+ public Set listSessionKeys() {
+ return Collections.emptySet();
+ }
+}
diff --git a/easy-agents-agent-runtime/src/main/java/com/easyagents/agent/runtime/skill/AgentSkillBinding.java b/easy-agents-agent-runtime/src/main/java/com/easyagents/agent/runtime/skill/AgentSkillBinding.java
new file mode 100644
index 0000000..f009fb0
--- /dev/null
+++ b/easy-agents-agent-runtime/src/main/java/com/easyagents/agent/runtime/skill/AgentSkillBinding.java
@@ -0,0 +1,52 @@
+package com.easyagents.agent.runtime.skill;
+
+/**
+ * Skill 与工具的运行时绑定关系。
+ */
+public class AgentSkillBinding {
+
+ private final String skillId;
+ private final String skillName;
+ private final String skillBoxId;
+
+ /**
+ * 创建 Skill 绑定关系。
+ *
+ * @param skillId Skill ID
+ * @param skillName Skill 名称
+ * @param skillBoxId SkillBox ID
+ */
+ public AgentSkillBinding(String skillId, String skillName, String skillBoxId) {
+ this.skillId = skillId;
+ this.skillName = skillName;
+ this.skillBoxId = skillBoxId;
+ }
+
+ /**
+ * 获取 Skill ID。
+ *
+ * @return Skill ID
+ */
+ public String getSkillId() {
+ return skillId;
+ }
+
+ /**
+ * 获取 Skill 名称。
+ *
+ * @return Skill 名称
+ */
+ public String getSkillName() {
+ return skillName;
+ }
+
+ /**
+ * 获取 SkillBox ID。
+ *
+ * @return SkillBox ID
+ */
+ public String getSkillBoxId() {
+ return skillBoxId;
+ }
+}
+
diff --git a/easy-agents-agent-runtime/src/main/java/com/easyagents/agent/runtime/skill/AgentSkillBoxSpec.java b/easy-agents-agent-runtime/src/main/java/com/easyagents/agent/runtime/skill/AgentSkillBoxSpec.java
new file mode 100644
index 0000000..0707a4b
--- /dev/null
+++ b/easy-agents-agent-runtime/src/main/java/com/easyagents/agent/runtime/skill/AgentSkillBoxSpec.java
@@ -0,0 +1,147 @@
+package com.easyagents.agent.runtime.skill;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 声明式 SkillBox 配置。
+ */
+public class AgentSkillBoxSpec {
+
+ private String skillBoxId;
+ private List skills = new ArrayList<>();
+ private Map> toolBindings = new LinkedHashMap<>();
+ private List enabledToolNames = new ArrayList<>();
+ private List disabledToolNames = new ArrayList<>();
+ private Map> presetParameters = new LinkedHashMap<>();
+ private boolean exposeAllSkillMetadata;
+
+ /**
+ * 获取SkillBox ID。
+ *
+ * @return SkillBox ID 标识
+ */
+ public String getSkillBoxId() {
+ return skillBoxId;
+ }
+
+ /**
+ * 设置SkillBox ID。
+ *
+ * @param skillBoxId SkillBox ID 标识
+ */
+ public void setSkillBoxId(String skillBoxId) {
+ this.skillBoxId = skillBoxId;
+ }
+
+ /**
+ * 获取Skill 列表。
+ *
+ * @return Skill 列表
+ */
+ public List