diff --git a/cameleer3-server-app/src/main/java/com/cameleer3/server/app/storage/ClickHouseAgentEventRepository.java b/cameleer3-server-app/src/main/java/com/cameleer3/server/app/storage/ClickHouseAgentEventRepository.java
new file mode 100644
index 00000000..1ddbad44
--- /dev/null
+++ b/cameleer3-server-app/src/main/java/com/cameleer3/server/app/storage/ClickHouseAgentEventRepository.java
@@ -0,0 +1,73 @@
+package com.cameleer3.server.app.storage;
+
+import com.cameleer3.server.core.agent.AgentEventRecord;
+import com.cameleer3.server.core.agent.AgentEventRepository;
+import org.springframework.jdbc.core.JdbcTemplate;
+
+import java.sql.Timestamp;
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * ClickHouse implementation of {@link AgentEventRepository}.
+ *
+ * The ClickHouse table has no {@code id} column (no BIGSERIAL equivalent),
+ * so all returned {@link AgentEventRecord} instances have {@code id = 0}.
+ */
+public class ClickHouseAgentEventRepository implements AgentEventRepository {
+
+ private static final String TENANT = "default";
+
+ private static final String INSERT_SQL =
+ "INSERT INTO agent_events (tenant_id, agent_id, app_id, event_type, detail) VALUES (?, ?, ?, ?, ?)";
+
+ private static final String SELECT_BASE =
+ "SELECT 0 AS id, agent_id, app_id, event_type, detail, timestamp FROM agent_events WHERE tenant_id = ?";
+
+ private final JdbcTemplate jdbc;
+
+ public ClickHouseAgentEventRepository(JdbcTemplate jdbc) {
+ this.jdbc = jdbc;
+ }
+
+ @Override
+ public void insert(String agentId, String appId, String eventType, String detail) {
+ jdbc.update(INSERT_SQL, TENANT, agentId, appId, eventType, detail);
+ }
+
+ @Override
+ public List query(String appId, String agentId, Instant from, Instant to, int limit) {
+ var sql = new StringBuilder(SELECT_BASE);
+ var params = new ArrayList