diff --git a/chainfire b/chainfire
deleted file mode 160000
index 0d970d8..0000000
--- a/chainfire
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit 0d970d80331e2a0d74a6c806f3576095bd083923
diff --git a/chainfire/.gitignore b/chainfire/.gitignore
new file mode 100644
index 0000000..9391cf5
--- /dev/null
+++ b/chainfire/.gitignore
@@ -0,0 +1,22 @@
+# Generated files
+/target/
+**/*.rs.bk
+Cargo.lock
+
+# IDE
+.idea/
+.vscode/
+*.swp
+*.swo
+
+# OS
+.DS_Store
+Thumbs.db
+
+# Test data
+/tmp/
+*.db/
+
+# Environment
+.env
+.env.local
diff --git a/chainfire/Cargo.toml b/chainfire/Cargo.toml
new file mode 100644
index 0000000..3647999
--- /dev/null
+++ b/chainfire/Cargo.toml
@@ -0,0 +1,89 @@
+[workspace]
+resolver = "2"
+members = [
+ "crates/chainfire-proto",
+ "crates/chainfire-types",
+ "crates/chainfire-storage",
+ "crates/chainfire-raft",
+ "crates/chainfire-gossip",
+ "crates/chainfire-watch",
+ "crates/chainfire-api",
+ "crates/chainfire-core",
+ "crates/chainfire-server",
+ "chainfire-client",
+]
+
+[workspace.package]
+version = "0.1.0"
+edition = "2021"
+license = "MIT OR Apache-2.0"
+rust-version = "1.75"
+authors = ["Chainfire Contributors"]
+repository = "https://github.com/chainfire/chainfire"
+
+[workspace.dependencies]
+# Internal crates
+chainfire-types = { path = "crates/chainfire-types" }
+chainfire-storage = { path = "crates/chainfire-storage" }
+chainfire-raft = { path = "crates/chainfire-raft" }
+chainfire-gossip = { path = "crates/chainfire-gossip" }
+chainfire-watch = { path = "crates/chainfire-watch" }
+chainfire-api = { path = "crates/chainfire-api" }
+chainfire-client = { path = "chainfire-client" }
+chainfire-core = { path = "crates/chainfire-core" }
+chainfire-server = { path = "crates/chainfire-server" }
+chainfire-proto = { path = "crates/chainfire-proto" }
+
+# Async runtime
+tokio = { version = "1.40", features = ["full"] }
+tokio-stream = "0.1"
+futures = "0.3"
+async-trait = "0.1"
+
+# Raft
+openraft = { version = "0.9", features = ["serde", "storage-v2"] }
+
+# Gossip (SWIM protocol)
+foca = { version = "1.0", features = ["std", "tracing", "serde", "postcard-codec"] }
+
+# Storage
+rocksdb = { version = "0.24", default-features = false, features = ["multi-threaded-cf", "zstd", "lz4", "snappy"] }
+
+# gRPC
+tonic = "0.12"
+tonic-build = "0.12"
+tonic-health = "0.12"
+prost = "0.13"
+prost-types = "0.13"
+
+# Serialization
+serde = { version = "1.0", features = ["derive"] }
+serde_json = "1.0"
+bincode = "1.3"
+
+# Utilities
+thiserror = "1.0"
+anyhow = "1.0"
+tracing = "0.1"
+tracing-subscriber = { version = "0.3", features = ["env-filter"] }
+bytes = "1.5"
+parking_lot = "0.12"
+dashmap = "6"
+
+# Metrics
+metrics = "0.23"
+metrics-exporter-prometheus = "0.15"
+
+# Configuration
+toml = "0.8"
+clap = { version = "4", features = ["derive"] }
+
+# Testing
+tempfile = "3.10"
+proptest = "1.4"
+
+[workspace.lints.rust]
+unsafe_code = "deny"
+
+[workspace.lints.clippy]
+all = "warn"
diff --git a/chainfire/advice.md b/chainfire/advice.md
new file mode 100644
index 0000000..7517167
--- /dev/null
+++ b/chainfire/advice.md
@@ -0,0 +1,87 @@
+RaftとGossipプロトコルを用いた、クラスター管理のための数万台までスケールするKey-Value Storeを書いてほしいです。
+
+- プログラミング言語:rust
+- テストをちゃんと書きながら書くことを推奨する。
+- クラスターへの参加/削除/障害検知を行う。
+
+では、**「Raft(合意形成)」と「Gossip(情報の拡散)」を組み合わせた場合、具体的にどうデータが流れ、どうやってノードが動き出すのか**、その具体的なフローを解説します。
+
+-----
+
+### 前提:このシステムの役割分担
+
+ * **Control Plane (CP):** Raftで構成された3〜7台(Raftアルゴリズムでうまく合意が取れる範囲)のサーバー。情報の「正規の持ち主」。いなくなったら自動でWorker Nodesから昇格する。
+ * **Worker Nodes (VM/DB Hosts):** 数百〜数千台の実働部隊。CPのクライアント。
+
+### 1\. データはどのように書き込まれるか? (Write)
+
+書き込みは **「必ず Control Plane の Raft Leader に対して」** 行います。Gossip経由での書き込みは(順序保証がないため)行いません。
+
+例:「VM-A を Node-10 で起動したい」
+
+1. **API Call:** 管理者(またはCLI)が、CPのAPIサーバーにリクエストを送ります。
+2. **Raft Log:** CPのリーダーは、この変更を `Put(Key="/nodes/node-10/tasks/vm-a", Value="START")` としてRaftログに追加します。
+3. **Commit:** 過半数のCPノードがログを保存したら「書き込み完了」と見なします。
+
+ここまでは普通のDBと同じです。
+
+### 2\. 各ノードはどのようにデータを取得し、通知を受けるか? (Read & Notify)
+
+ここが最大のポイントです。数千台のノードが「自分宛ての命令はないか?」と毎秒ポーリング(問い合わせ)すると、CPがDDoS攻撃を受けたようにパンクします。
+
+ここで **「Watch(ロングポーリング)」** という仕組みを使います。
+
+#### A. Watchによる通知と取得(これがメイン)
+
+Kubernetesやetcdが採用している方式です。
+
+1. **接続維持:** Node-10 は起動時に CP に対して `Watch("/nodes/node-10/")` というリクエストを送ります。
+2. **待機:** CP は「Node-10 以下のキーに変更があるまで、レスポンスを返さずに接続を維持(ブロック)」します。
+3. **イベント発火:** 先ほどの書き込み(VM起動命令)が発生した瞬間、CP は待機していた Node-10 への接続を通じて「更新イベント(Event: PUT, Key: ...vm-a, Value: START)」を即座にプッシュします。
+4. **アクション:** Node-10 は通知を受け取り次第、VMを起動します。
+
+**結論:** 「書き込み後の通知」は絶対に必要です。それを効率よくやるのが **Watch API** です。
+
+-----
+
+### 3\. じゃあ Gossip はどこで使うのか?
+
+「Raft + Watch」で完結しそうに見えますが、10台以上のスケール、特にVM基盤のような動的な環境では **Gossip が以下の「3つの穴」を埋めるために不可欠** になります。
+
+#### ① Nodeの死活監視・インベントリ管理(下り方向)
+
+CPが「Node-10にVMを立てたい」と思ったとき、「そもそもNode-10は生きているのか? IPは? 空きメモリは?」という情報を知る必要があります。
+
+ * **Gossipの役割:** 各Worker Nodeは、Gossip(SWIMプロトコル)でお互いに、そしてCPに対して「自分は生きてるよ、IPはこれだよ」と喋り続けます。
+ * CPはこのGossip情報を聞いて、最新の「ノード一覧リスト(Memberlist)」をメモリ上に保持します。
+
+#### ② サービスのディスカバリ(横方向)
+
+DB基盤の場合、「DBノードA」が「DBノードB」と通信したいことがあります。いちいちCPに聞きに行くと遅いです。
+
+ * **Gossipの役割:** ノード同士で「私はここにいるよ」とGossipし合うことで、CPを介さずに直接通信相手を見つけられます。
+
+#### ③ "Actual State"(現状)の報告(上り方向)
+
+VMが起動した後、「起動しました」というステータスをどうCPに伝えるか。
+
+ * **Raftに書く:** 確実ですが、頻繁に変わるステータス(CPU使用率など)を全部Raftに書くとCPがパンクします。
+ * **Gossipで流す:** 「VM-Aは起動中、負荷50%」といった情報はGossipに乗せて、**「結果的にCPに伝わればいい」** という扱いにします。
+
+-----
+
+### 設計のまとめ:RaftとGossipの使い分け
+
+これから作る「汎用管理DB(外部依存型)」は、以下のようなインターフェースを持つことになるでしょう。
+
+| アクション | 通信方式 | 具体的なデータ例 |
+| :--- | :--- | :--- |
+| **命令 (Desired State)**
「こうなってほしい」 | **Raft + Watch**
(強整合性) | ・VMの起動/停止命令
・DBのデータ配置情報の変更
・パスワードや設定変更 |
+| **現状 (Actual State)**
「今こうなってます」 | **Gossip**
(結果整合性) | ・ノードの生存確認 (Heartbeat)
・リソース使用率 (CPU/Mem)
・「VM起動完了」などのステータス |
+| **通知 (Notification)** | **Watch (HTTP/gRPC Stream)** | ・「新しい命令が来たぞ!」というトリガー |
+
+#### 実装のアドバイス
+
+もし「etcdのようなもの」を自作されるなら、**「Serf (Gossip)」と「Raft」をライブラリとして組み込み、その上に「gRPCによるWatch付きのKVS API」を被せる** という構成になります。
+
+これができれば、VM基盤は「Watchして、VMを起動して、Gossipでステータスを返すエージェント」を作るだけで済みますし、DB基盤も同様に作れます。非常にスケーラブルで美しい設計です。
diff --git a/chainfire/chainfire-client/Cargo.toml b/chainfire/chainfire-client/Cargo.toml
new file mode 100644
index 0000000..ba9dd90
--- /dev/null
+++ b/chainfire/chainfire-client/Cargo.toml
@@ -0,0 +1,31 @@
+[package]
+name = "chainfire-client"
+version.workspace = true
+edition.workspace = true
+license.workspace = true
+rust-version.workspace = true
+description = "Chainfire distributed KVS client library"
+
+[dependencies]
+chainfire-types = { workspace = true }
+chainfire-proto = { workspace = true }
+
+# gRPC
+tonic = { workspace = true }
+
+# Async
+tokio = { workspace = true }
+tokio-stream = { workspace = true }
+futures = { workspace = true }
+
+# Utilities
+tracing = { workspace = true }
+thiserror = { workspace = true }
+serde = { workspace = true }
+serde_json = { workspace = true }
+
+[dev-dependencies]
+tokio = { workspace = true, features = ["rt-multi-thread", "macros"] }
+
+[lints]
+workspace = true
diff --git a/chainfire/chainfire-client/src/client.rs b/chainfire/chainfire-client/src/client.rs
new file mode 100644
index 0000000..11bb272
--- /dev/null
+++ b/chainfire/chainfire-client/src/client.rs
@@ -0,0 +1,389 @@
+//! Chainfire client implementation
+
+use crate::error::{ClientError, Result};
+use crate::watch::WatchHandle;
+use chainfire_proto::proto::{
+ cluster_client::ClusterClient,
+ compare,
+ kv_client::KvClient,
+ request_op,
+ response_op,
+ watch_client::WatchClient,
+ Compare,
+ DeleteRangeRequest,
+ PutRequest,
+ RangeRequest,
+ RequestOp,
+ StatusRequest,
+ TxnRequest,
+};
+use tonic::transport::Channel;
+use tracing::debug;
+
+/// Chainfire client
+pub struct Client {
+ /// gRPC channel
+ channel: Channel,
+ /// KV client
+ kv: KvClient,
+ /// Cluster client
+ cluster: ClusterClient,
+}
+
+impl Client {
+ /// Connect to a Chainfire server
+ pub async fn connect(addr: impl AsRef) -> Result {
+ let addr = addr.as_ref().to_string();
+ debug!(addr = %addr, "Connecting to Chainfire");
+
+ let channel = Channel::from_shared(addr)
+ .map_err(|e| ClientError::Connection(e.to_string()))?
+ .connect()
+ .await?;
+
+ let kv = KvClient::new(channel.clone());
+ let cluster = ClusterClient::new(channel.clone());
+
+ Ok(Self {
+ channel,
+ kv,
+ cluster,
+ })
+ }
+
+ /// Put a key-value pair
+ pub async fn put(&mut self, key: impl AsRef<[u8]>, value: impl AsRef<[u8]>) -> Result {
+ let resp = self
+ .kv
+ .put(PutRequest {
+ key: key.as_ref().to_vec(),
+ value: value.as_ref().to_vec(),
+ lease: 0,
+ prev_kv: false,
+ })
+ .await?
+ .into_inner();
+
+ Ok(resp.header.map(|h| h.revision as u64).unwrap_or(0))
+ }
+
+ /// Put a key-value pair with string values
+ pub async fn put_str(&mut self, key: &str, value: &str) -> Result {
+ self.put(key.as_bytes(), value.as_bytes()).await
+ }
+
+ /// Get a value by key
+ pub async fn get(&mut self, key: impl AsRef<[u8]>) -> Result